Compare commits
92 Commits
v2.0
...
adsb_encod
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59d21de725 | ||
|
|
560b737739 | ||
|
|
d50f05d00d | ||
|
|
11a560dbb5 | ||
|
|
b51facbdc6 | ||
|
|
d77d1f7f6b | ||
|
|
aff0f75de2 | ||
|
|
5286355bf6 | ||
|
|
fe9e033a64 | ||
|
|
57307109e6 | ||
|
|
6571fe6fc0 | ||
|
|
aa64d4e7a9 | ||
|
|
db57d7419f | ||
|
|
ed18352c0c | ||
|
|
a75d6fd050 | ||
|
|
0ff628bb8e | ||
|
|
ea7653ef79 | ||
|
|
2046b1de07 | ||
|
|
768b80df8e | ||
|
|
e52d43f963 | ||
|
|
b60b31d4fb | ||
|
|
cb67c63326 | ||
|
|
bddaf9aec6 | ||
|
|
00fc1475ff | ||
|
|
489c405de0 | ||
|
|
ec2721cfdc | ||
|
|
2f13524a7c | ||
|
|
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 | ||
|
|
7267859548 | ||
|
|
7a3bd089c4 | ||
|
|
17fdaca1c7 | ||
|
|
6f6b50776d | ||
|
|
0b7b9ad3dd | ||
|
|
24806f7e88 | ||
|
|
57ad40ec57 | ||
|
|
d230bdc4a9 | ||
|
|
4a6d3334a7 | ||
|
|
9f371fe86b | ||
|
|
0ea4969393 | ||
|
|
312c77629b | ||
|
|
6159691c3d | ||
|
|
7edbf3fd30 | ||
|
|
e19d0cd945 | ||
|
|
6fc68841ce | ||
|
|
f27fe6c8aa | ||
|
|
81a46e5070 | ||
|
|
6c2adbe990 | ||
|
|
3c14579040 | ||
|
|
fc9b05b6f1 | ||
|
|
b813e41343 | ||
|
|
c9159feb7d | ||
|
|
8cd5655a04 | ||
|
|
8ded3500d4 | ||
|
|
c348a2295d | ||
|
|
040a1879c9 | ||
|
|
61b55531e8 | ||
|
|
abdafd7dea | ||
|
|
b97299ce1b | ||
|
|
652ef65bbb | ||
|
|
bfdb8221a0 | ||
|
|
32e6ee3904 | ||
|
|
d82dfd5537 |
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 tests
|
||||||
|
|
||||||
|
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/*
|
||||||
270
README.rst
270
README.rst
@@ -1,107 +1,138 @@
|
|||||||
The Python ADS-B/Mode-S Decoder
|
The Python ADS-B/Mode-S Decoder
|
||||||
==========================================
|
===============================
|
||||||
|
|
||||||
Python library for ADS-B/Mode-S message decoding. Supported Downlink Formats (DF) are:
|
PyModeS is a Python library designed to decode Mode-S (including ADS-B) message. It can be imported to your python project or used as a standalone tool to view and save live traffic data.
|
||||||
|
|
||||||
**DF17 / DF18: Automatic Dependent Surveillance - Broadcast (ADS-B)**
|
This is a project created by Junzi Sun, who works at `TU Delft <https://www.tudelft.nl/en/>`_, `Aerospace Engineering Faculty <https://www.tudelft.nl/en/ae/>`_, `CNS/ATM research group <http://cs.lr.tudelft.nl/atm/>`_. It is supported by many `contributors <https://github.com/junzis/pyModeS/graphs/contributors>`_ from different institutions.
|
||||||
|
|
||||||
- TC=1-4 / BDS 0,8: Aircraft identification and category
|
Introduction
|
||||||
- TC=5-8 / BDS 0,6: Surface position
|
------------
|
||||||
- TC=9-18 / BDS 0,5: Airborne position
|
|
||||||
- TC=19 / BDS 0,9: Airborne velocity
|
pyModeS supports the decoding of following types of messages:
|
||||||
- TC=28 / BDS 6,1: Airborne status [to be implemented]
|
|
||||||
- TC=29 / BDS 6,2: Target state and status information [to be implemented]
|
- DF4 / DF20: Altitude code
|
||||||
- TC=31 / BDS 6,5: Aircraft operational status [to be implemented]
|
- DF5 / DF21: Identity code (squawk code)
|
||||||
|
|
||||||
|
- DF17 / DF18: Automatic Dependent Surveillance-Broadcast (ADS-B)
|
||||||
|
|
||||||
|
- TC=1-4 / BDS 0,8: Aircraft identification and category
|
||||||
|
- TC=5-8 / BDS 0,6: Surface position
|
||||||
|
- TC=9-18 / BDS 0,5: Airborne position
|
||||||
|
- TC=19 / BDS 0,9: Airborne velocity
|
||||||
|
- TC=28 / BDS 6,1: Airborne status [to be implemented]
|
||||||
|
- TC=29 / BDS 6,2: Target state and status information [to be implemented]
|
||||||
|
- TC=31 / BDS 6,5: Aircraft operational status [to be implemented]
|
||||||
|
|
||||||
|
- DF20 / DF21: Mode-S Comm-B messages
|
||||||
|
|
||||||
|
- BDS 1,0: Data link capability report
|
||||||
|
- BDS 1,7: Common usage GICB capability report
|
||||||
|
- BDS 2,0: Aircraft identification
|
||||||
|
- BDS 3,0: ACAS active resolution advisory
|
||||||
|
- BDS 4,0: Selected vertical intention
|
||||||
|
- BDS 4,4: Meteorological routine air report (experimental)
|
||||||
|
- BDS 4,5: Meteorological hazard report (experimental)
|
||||||
|
- BDS 5,0: Track and turn report
|
||||||
|
- BDS 6,0: Heading and speed report
|
||||||
|
|
||||||
|
|
||||||
**DF20 / DF21: Mode-S Comm-B replies**
|
|
||||||
|
|
||||||
- BDS 1,0: Data link capability report
|
If you find this project useful for your research, please considering cite this tool as::
|
||||||
- BDS 1,7: Common usage GICB capability report
|
|
||||||
- BDS 2,0: Aircraft identification
|
@article{sun2019pymodes,
|
||||||
- BDS 2,1: Aircraft and airline registration markings
|
author={J. {Sun} and H. {V\^u} and J. {Ellerbroek} and J. M. {Hoekstra}},
|
||||||
- BDS 3,0: ACAS active resolution advisory
|
journal={IEEE Transactions on Intelligent Transportation Systems},
|
||||||
- BDS 4,0: Selected vertical intention
|
title={pyModeS: Decoding Mode-S Surveillance Data for Open Air Transportation Research},
|
||||||
- BDS 4,4: Meteorological routine air report
|
year={2019},
|
||||||
- BDS 5,0: Track and turn report
|
doi={10.1109/TITS.2019.2914770},
|
||||||
- BDS 5,3: Air-referenced state vector
|
ISSN={1524-9050},
|
||||||
- BDS 6,0: Heading and speed report
|
}
|
||||||
|
|
||||||
|
|
||||||
**DF4 / DF20: Altitude code**
|
|
||||||
|
|
||||||
**DF5 / DF21: Identity code (squawk code)**
|
|
||||||
|
|
||||||
Detailed manual on Mode-S decoding is published by the author, at:
|
|
||||||
https://mode-s.org/decode
|
|
||||||
|
|
||||||
|
|
||||||
New features in v2.0
|
Resources
|
||||||
---------------------
|
|
||||||
- New structure of the libraries
|
|
||||||
- ADS-B and Comm-B data streaming
|
|
||||||
- Active aircraft viewing (terminal curses)
|
|
||||||
- Improved BDS identification
|
|
||||||
- Optimizing decoding speed
|
|
||||||
|
|
||||||
|
|
||||||
Source code
|
|
||||||
-----------
|
-----------
|
||||||
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
|
||||||
|
|
||||||
API documentation at:
|
Detailed manual on Mode-S decoding is published at:
|
||||||
http://pymodes.readthedocs.io
|
https://mode-s.org/decode
|
||||||
[To be updated]
|
|
||||||
|
The API documentation of pyModeS is at:
|
||||||
|
https://mode-s.org/api
|
||||||
|
|
||||||
|
|
||||||
Install
|
|
||||||
-------
|
|
||||||
|
|
||||||
To install latest version from the GitHub:
|
Basic installation
|
||||||
|
-------------------
|
||||||
|
|
||||||
::
|
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.
|
||||||
|
|
||||||
::
|
Advanced installation (using c modules)
|
||||||
|
------------------------------------------
|
||||||
|
|
||||||
pip install pyModeS
|
If you want to make use of the (faster) c module, install ``pyModeS`` as follows::
|
||||||
|
|
||||||
|
git clone https://github.com/junzis/pyModeS
|
||||||
|
cd pyModeS
|
||||||
|
make ext
|
||||||
|
make install
|
||||||
|
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
.. image:: https://github.com/junzis/pyModeS/raw/master/doc/modeslive-screenshot.png
|
.. image:: https://github.com/junzis/pyModeS/raw/master/doc/modeslive-screenshot.png
|
||||||
:width: 700px
|
:width: 700px
|
||||||
|
|
||||||
|
|
||||||
Use the library
|
Use the library
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
@@ -122,7 +153,7 @@ Common functions
|
|||||||
pms.hex2bin(str) # Convert hexadecimal string to binary string
|
pms.hex2bin(str) # Convert hexadecimal string to binary string
|
||||||
pms.bin2int(str) # Convert binary string to integer
|
pms.bin2int(str) # Convert binary string to integer
|
||||||
pms.hex2int(str) # Convert hexadecimal string to integer
|
pms.hex2int(str) # Convert hexadecimal string to integer
|
||||||
pms.gray2int(str) # Convert grey code to interger
|
pms.gray2int(str) # Convert grey code to integer
|
||||||
|
|
||||||
|
|
||||||
Core functions for ADS-B decoding
|
Core functions for ADS-B decoding
|
||||||
@@ -136,10 +167,11 @@ Core functions for ADS-B decoding
|
|||||||
# Typecode 1-4
|
# Typecode 1-4
|
||||||
pms.adsb.callsign(msg)
|
pms.adsb.callsign(msg)
|
||||||
|
|
||||||
# Typecode 5-8 (surface), 9-18 (airborne, barometric height), and 9-18 (airborne, GNSS height)
|
# Typecode 5-8 (surface), 9-18 (airborne, barometric height), and 20-22 (airborne, GNSS height)
|
||||||
pms.adsb.position(msg_even, msg_odd, t_even, t_odd, lat_ref=None, lon_ref=None)
|
pms.adsb.position(msg_even, msg_odd, t_even, t_odd, lat_ref=None, lon_ref=None)
|
||||||
pms.adsb.airborne_position(msg_even, msg_odd, t_even, t_odd)
|
pms.adsb.airborne_position(msg_even, msg_odd, t_even, t_odd)
|
||||||
pms.adsb.surface_position(msg_even, msg_odd, t_even, t_odd, lat_ref, lon_ref)
|
pms.adsb.surface_position(msg_even, msg_odd, t_even, t_odd, lat_ref, lon_ref)
|
||||||
|
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)
|
||||||
@@ -150,15 +182,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
|
||||||
@@ -214,19 +241,19 @@ Mode-S Enhanced Surveillance (EHS)
|
|||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
# For BDS register 4,0
|
# BDS 4,0
|
||||||
pms.commb.alt40mcp(msg) # MCP/FCU selected altitude (ft)
|
pms.commb.selalt40mcp(msg) # MCP/FCU selected altitude (ft)
|
||||||
pms.commb.alt40fms(msg) # FMS selected altitude (ft)
|
pms.commb.selalt40fms(msg) # FMS selected altitude (ft)
|
||||||
pms.commb.p40baro(msg) # Barometric pressure (mb)
|
pms.commb.p40baro(msg) # Barometric pressure (mb)
|
||||||
|
|
||||||
# For BDS register 5,0
|
# BDS 5,0
|
||||||
pms.commb.roll50(msg) # Roll angle (deg)
|
pms.commb.roll50(msg) # Roll angle (deg)
|
||||||
pms.commb.trk50(msg) # True track angle (deg)
|
pms.commb.trk50(msg) # True track angle (deg)
|
||||||
pms.commb.gs50(msg) # Ground speed (kt)
|
pms.commb.gs50(msg) # Ground speed (kt)
|
||||||
pms.commb.rtrk50(msg) # Track angle rate (deg/sec)
|
pms.commb.rtrk50(msg) # Track angle rate (deg/sec)
|
||||||
pms.commb.tas50(msg) # True airspeed (kt)
|
pms.commb.tas50(msg) # True airspeed (kt)
|
||||||
|
|
||||||
# For BDS register 6,0
|
# BDS 6,0
|
||||||
pms.commb.hdg60(msg) # Magnetic heading (deg)
|
pms.commb.hdg60(msg) # Magnetic heading (deg)
|
||||||
pms.commb.ias60(msg) # Indicated airspeed (kt)
|
pms.commb.ias60(msg) # Indicated airspeed (kt)
|
||||||
pms.commb.mach60(msg) # Mach number (-)
|
pms.commb.mach60(msg) # Mach number (-)
|
||||||
@@ -235,21 +262,90 @@ Mode-S Enhanced Surveillance (EHS)
|
|||||||
|
|
||||||
|
|
||||||
Meteorological routine air report (MRAR) [Experimental]
|
Meteorological routine air report (MRAR) [Experimental]
|
||||||
|
********************************************************
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
# BDS 4,4
|
||||||
|
pms.commb.wind44(msg) # Wind speed (kt) and direction (true) (deg)
|
||||||
|
pms.commb.temp44(msg) # Static air temperature (C)
|
||||||
|
pms.commb.p44(msg) # Average static pressure (hPa)
|
||||||
|
pms.commb.hum44(msg) # Humidity (%)
|
||||||
|
|
||||||
|
|
||||||
|
Meteorological hazard air report (MHR) [Experimental]
|
||||||
*******************************************************
|
*******************************************************
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
# For BDS register 4,4
|
# BDS 4,5
|
||||||
pms.commb.wind44(msg, rev=False) # Wind speed (kt) and direction (true) (deg)
|
pms.commb.turb45(msg) # Turbulence level (0-3)
|
||||||
pms.commb.temp44(msg, rev=False) # Static air temperature (C)
|
pms.commb.ws45(msg) # Wind shear level (0-3)
|
||||||
pms.commb.p44(msg, rev=False) # Average static pressure (hPa)
|
pms.commb.mb45(msg) # Microburst level (0-3)
|
||||||
pms.commb.hum44(msg, rev=False) # Humidity (%)
|
pms.commb.ic45(msg) # Icing level (0-3)
|
||||||
|
pms.commb.wv45(msg) # Wake vortex level (0-3)
|
||||||
|
pms.commb.temp45(msg) # Static air temperature (C)
|
||||||
|
pms.commb.p45(msg) # Average static pressure (hPa)
|
||||||
|
pms.commb.rh45(msg) # Radio height (ft)
|
||||||
|
|
||||||
|
|
||||||
Developement
|
|
||||||
------------
|
|
||||||
To perform unit tests. First install ``tox`` through pip, Then, run the following commands:
|
|
||||||
|
|
||||||
.. code:: bash
|
Customize the streaming module
|
||||||
|
******************************
|
||||||
|
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.
|
||||||
|
|
||||||
$ tox
|
Here is an example:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
import pyModeS as pms
|
||||||
|
from pyModeS.extra.tcpclient import TcpClient
|
||||||
|
|
||||||
|
# define your custom class by extending the TcpClient
|
||||||
|
# - implement your handle_messages() methods
|
||||||
|
class ADSBClient(TcpClient):
|
||||||
|
def __init__(self, host, port, rawtype):
|
||||||
|
super(ADSBClient, self).__init__(host, port, rawtype)
|
||||||
|
|
||||||
|
def handle_messages(self, messages):
|
||||||
|
for msg, ts in messages:
|
||||||
|
if len(msg) != 28: # wrong data length
|
||||||
|
continue
|
||||||
|
|
||||||
|
df = pms.df(msg)
|
||||||
|
|
||||||
|
if df != 17: # not ADSB
|
||||||
|
continue
|
||||||
|
|
||||||
|
if pms.crc(msg) !=0: # CRC fail
|
||||||
|
continue
|
||||||
|
|
||||||
|
icao = pms.adsb.icao(msg)
|
||||||
|
tc = pms.adsb.typecode(msg)
|
||||||
|
|
||||||
|
# TODO: write you magic code here
|
||||||
|
print(ts, icao, tc, msg)
|
||||||
|
|
||||||
|
# run new client, change the host, port, and rawtype if needed
|
||||||
|
client = ADSBClient(host='127.0.0.1', port=30005, rawtype='beast')
|
||||||
|
client.run()
|
||||||
|
|
||||||
|
|
||||||
|
Unit test
|
||||||
|
---------
|
||||||
|
To perform unit tests, ``pytest`` must be install first.
|
||||||
|
|
||||||
|
Build Cython extensions
|
||||||
|
::
|
||||||
|
|
||||||
|
$ make ext
|
||||||
|
|
||||||
|
Run unit tests
|
||||||
|
::
|
||||||
|
|
||||||
|
$ make test
|
||||||
|
|
||||||
|
Clean build files
|
||||||
|
::
|
||||||
|
|
||||||
|
$ make clean
|
||||||
|
|||||||
3
doc/.gitignore
vendored
3
doc/.gitignore
vendored
@@ -1,3 +0,0 @@
|
|||||||
_build
|
|
||||||
_static
|
|
||||||
_templates
|
|
||||||
228
doc/Makefile
228
doc/Makefile
@@ -1,225 +1,21 @@
|
|||||||
# Makefile for Sphinx documentation
|
# Minimal makefile for Sphinx documentation
|
||||||
#
|
#
|
||||||
|
|
||||||
# You can set these variables from the command line.
|
# You can set these variables from the command line.
|
||||||
SPHINXOPTS =
|
SPHINXOPTS =
|
||||||
SPHINXBUILD = sphinx-build
|
SPHINXBUILD = sphinx-build
|
||||||
PAPER =
|
SOURCEDIR = source
|
||||||
BUILDDIR = _build
|
BUILDDIR = build
|
||||||
|
|
||||||
# Internal variables.
|
# Put it first so that "make" without argument is like "make help".
|
||||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
|
||||||
PAPEROPT_letter = -D latex_paper_size=letter
|
|
||||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
|
||||||
# the i18n builder cannot share the environment and doctrees with the others
|
|
||||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
|
||||||
|
|
||||||
.PHONY: help
|
|
||||||
help:
|
help:
|
||||||
@echo "Please use \`make <target>' where <target> is one of"
|
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||||
@echo " html to make standalone HTML files"
|
|
||||||
@echo " dirhtml to make HTML files named index.html in directories"
|
|
||||||
@echo " singlehtml to make a single large HTML file"
|
|
||||||
@echo " pickle to make pickle files"
|
|
||||||
@echo " json to make JSON files"
|
|
||||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
|
||||||
@echo " qthelp to make HTML files and a qthelp project"
|
|
||||||
@echo " applehelp to make an Apple Help Book"
|
|
||||||
@echo " devhelp to make HTML files and a Devhelp project"
|
|
||||||
@echo " epub to make an epub"
|
|
||||||
@echo " epub3 to make an epub3"
|
|
||||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
|
||||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
|
||||||
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
|
|
||||||
@echo " text to make text files"
|
|
||||||
@echo " man to make manual pages"
|
|
||||||
@echo " texinfo to make Texinfo files"
|
|
||||||
@echo " info to make Texinfo files and run them through makeinfo"
|
|
||||||
@echo " gettext to make PO message catalogs"
|
|
||||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
|
||||||
@echo " xml to make Docutils-native XML files"
|
|
||||||
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
|
|
||||||
@echo " linkcheck to check all external links for integrity"
|
|
||||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
|
||||||
@echo " coverage to run coverage check of the documentation (if enabled)"
|
|
||||||
@echo " dummy to check syntax errors of document sources"
|
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: help Makefile
|
||||||
clean:
|
|
||||||
rm -rf $(BUILDDIR)/*
|
|
||||||
|
|
||||||
.PHONY: html
|
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||||
html:
|
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
%: Makefile
|
||||||
@echo
|
rm -f source/pyModeS*.rst source/modules.rst
|
||||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
sphinx-apidoc -f -e -M -o source/ ../pyModeS ../pyModeS/decoder/ehs.py ../pyModeS/decoder/els.py ../pyModeS/streamer ../pyModeS/extra
|
||||||
|
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||||
.PHONY: dirhtml
|
|
||||||
dirhtml:
|
|
||||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
|
||||||
|
|
||||||
.PHONY: singlehtml
|
|
||||||
singlehtml:
|
|
||||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
|
||||||
|
|
||||||
.PHONY: pickle
|
|
||||||
pickle:
|
|
||||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
|
||||||
@echo
|
|
||||||
@echo "Build finished; now you can process the pickle files."
|
|
||||||
|
|
||||||
.PHONY: json
|
|
||||||
json:
|
|
||||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
|
||||||
@echo
|
|
||||||
@echo "Build finished; now you can process the JSON files."
|
|
||||||
|
|
||||||
.PHONY: htmlhelp
|
|
||||||
htmlhelp:
|
|
||||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
|
||||||
@echo
|
|
||||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
|
||||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
|
||||||
|
|
||||||
.PHONY: qthelp
|
|
||||||
qthelp:
|
|
||||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
|
||||||
@echo
|
|
||||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
|
||||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
|
||||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pyModeS.qhcp"
|
|
||||||
@echo "To view the help file:"
|
|
||||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pyModeS.qhc"
|
|
||||||
|
|
||||||
.PHONY: applehelp
|
|
||||||
applehelp:
|
|
||||||
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
|
|
||||||
@echo "N.B. You won't be able to view it unless you put it in" \
|
|
||||||
"~/Library/Documentation/Help or install it in your application" \
|
|
||||||
"bundle."
|
|
||||||
|
|
||||||
.PHONY: devhelp
|
|
||||||
devhelp:
|
|
||||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
|
||||||
@echo
|
|
||||||
@echo "Build finished."
|
|
||||||
@echo "To view the help file:"
|
|
||||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/pyModeS"
|
|
||||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pyModeS"
|
|
||||||
@echo "# devhelp"
|
|
||||||
|
|
||||||
.PHONY: epub
|
|
||||||
epub:
|
|
||||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
|
||||||
|
|
||||||
.PHONY: epub3
|
|
||||||
epub3:
|
|
||||||
$(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3."
|
|
||||||
|
|
||||||
.PHONY: latex
|
|
||||||
latex:
|
|
||||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
|
||||||
@echo
|
|
||||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
|
||||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
|
||||||
"(use \`make latexpdf' here to do that automatically)."
|
|
||||||
|
|
||||||
.PHONY: latexpdf
|
|
||||||
latexpdf:
|
|
||||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
|
||||||
@echo "Running LaTeX files through pdflatex..."
|
|
||||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
|
||||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
|
||||||
|
|
||||||
.PHONY: latexpdfja
|
|
||||||
latexpdfja:
|
|
||||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
|
||||||
@echo "Running LaTeX files through platex and dvipdfmx..."
|
|
||||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
|
|
||||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
|
||||||
|
|
||||||
.PHONY: text
|
|
||||||
text:
|
|
||||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
|
||||||
|
|
||||||
.PHONY: man
|
|
||||||
man:
|
|
||||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
|
||||||
|
|
||||||
.PHONY: texinfo
|
|
||||||
texinfo:
|
|
||||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
|
||||||
@echo "Run \`make' in that directory to run these through makeinfo" \
|
|
||||||
"(use \`make info' here to do that automatically)."
|
|
||||||
|
|
||||||
.PHONY: info
|
|
||||||
info:
|
|
||||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
|
||||||
@echo "Running Texinfo files through makeinfo..."
|
|
||||||
make -C $(BUILDDIR)/texinfo info
|
|
||||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
|
||||||
|
|
||||||
.PHONY: gettext
|
|
||||||
gettext:
|
|
||||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
|
||||||
|
|
||||||
.PHONY: changes
|
|
||||||
changes:
|
|
||||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
|
||||||
@echo
|
|
||||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
|
||||||
|
|
||||||
.PHONY: linkcheck
|
|
||||||
linkcheck:
|
|
||||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
|
||||||
@echo
|
|
||||||
@echo "Link check complete; look for any errors in the above output " \
|
|
||||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
|
||||||
|
|
||||||
.PHONY: doctest
|
|
||||||
doctest:
|
|
||||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
|
||||||
@echo "Testing of doctests in the sources finished, look at the " \
|
|
||||||
"results in $(BUILDDIR)/doctest/output.txt."
|
|
||||||
|
|
||||||
.PHONY: coverage
|
|
||||||
coverage:
|
|
||||||
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
|
|
||||||
@echo "Testing of coverage in the sources finished, look at the " \
|
|
||||||
"results in $(BUILDDIR)/coverage/python.txt."
|
|
||||||
|
|
||||||
.PHONY: xml
|
|
||||||
xml:
|
|
||||||
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
|
|
||||||
|
|
||||||
.PHONY: pseudoxml
|
|
||||||
pseudoxml:
|
|
||||||
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
|
|
||||||
|
|
||||||
.PHONY: dummy
|
|
||||||
dummy:
|
|
||||||
$(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. Dummy builder generates no files."
|
|
||||||
|
|||||||
7
doc/README.rst
Normal file
7
doc/README.rst
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
How to generate the apidoc
|
||||||
|
====================================
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
cd doc
|
||||||
|
make html
|
||||||
337
doc/conf.py
337
doc/conf.py
@@ -1,337 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# pyModeS documentation build configuration file, created by
|
|
||||||
# sphinx-quickstart on Tue Aug 16 15:47:05 2016.
|
|
||||||
#
|
|
||||||
# This file is execfile()d with the current directory set to its
|
|
||||||
# containing dir.
|
|
||||||
#
|
|
||||||
# Note that not all possible configuration values are present in this
|
|
||||||
# autogenerated file.
|
|
||||||
#
|
|
||||||
# All configuration values have a default; values that are commented out
|
|
||||||
# serve to show the default.
|
|
||||||
|
|
||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
|
||||||
# add these directories to sys.path here. If the directory is relative to the
|
|
||||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
|
||||||
#
|
|
||||||
# import os
|
|
||||||
# import sys
|
|
||||||
# sys.path.insert(0, os.path.abspath('.'))
|
|
||||||
|
|
||||||
# -- General configuration ------------------------------------------------
|
|
||||||
|
|
||||||
# If your documentation needs a minimal Sphinx version, state it here.
|
|
||||||
#
|
|
||||||
# needs_sphinx = '1.0'
|
|
||||||
|
|
||||||
# Add any Sphinx extension module names here, as strings. They can be
|
|
||||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
|
||||||
# ones.
|
|
||||||
extensions = [
|
|
||||||
'sphinx.ext.autodoc',
|
|
||||||
'sphinx.ext.doctest',
|
|
||||||
'sphinx.ext.todo',
|
|
||||||
'sphinx.ext.coverage',
|
|
||||||
]
|
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
|
||||||
templates_path = ['_templates']
|
|
||||||
|
|
||||||
# The suffix(es) of source filenames.
|
|
||||||
# You can specify multiple suffix as a list of string:
|
|
||||||
#
|
|
||||||
# source_suffix = ['.rst', '.md']
|
|
||||||
source_suffix = '.rst'
|
|
||||||
|
|
||||||
# The encoding of source files.
|
|
||||||
#
|
|
||||||
# source_encoding = 'utf-8-sig'
|
|
||||||
|
|
||||||
# The master toctree document.
|
|
||||||
master_doc = 'index'
|
|
||||||
|
|
||||||
# General information about the project.
|
|
||||||
project = u'pyModeS'
|
|
||||||
copyright = u'2016, Junzi Sun'
|
|
||||||
author = u'Junzi Sun'
|
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
|
||||||
# |version| and |release|, also used in various other places throughout the
|
|
||||||
# built documents.
|
|
||||||
#
|
|
||||||
# The short X.Y version.
|
|
||||||
version = u'1.0.5'
|
|
||||||
# The full version, including alpha/beta/rc tags.
|
|
||||||
release = u'1.0.5'
|
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
|
||||||
# for a list of supported languages.
|
|
||||||
#
|
|
||||||
# This is also used if you do content translation via gettext catalogs.
|
|
||||||
# Usually you set "language" from the command line for these cases.
|
|
||||||
language = None
|
|
||||||
|
|
||||||
# There are two options for replacing |today|: either, you set today to some
|
|
||||||
# non-false value, then it is used:
|
|
||||||
#
|
|
||||||
# today = ''
|
|
||||||
#
|
|
||||||
# Else, today_fmt is used as the format for a strftime call.
|
|
||||||
#
|
|
||||||
# today_fmt = '%B %d, %Y'
|
|
||||||
|
|
||||||
# List of patterns, relative to source directory, that match files and
|
|
||||||
# directories to ignore when looking for source files.
|
|
||||||
# This patterns also effect to html_static_path and html_extra_path
|
|
||||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
|
||||||
|
|
||||||
# The reST default role (used for this markup: `text`) to use for all
|
|
||||||
# documents.
|
|
||||||
#
|
|
||||||
# default_role = None
|
|
||||||
|
|
||||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
|
||||||
#
|
|
||||||
# add_function_parentheses = True
|
|
||||||
|
|
||||||
# If true, the current module name will be prepended to all description
|
|
||||||
# unit titles (such as .. function::).
|
|
||||||
#
|
|
||||||
# add_module_names = True
|
|
||||||
|
|
||||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
|
||||||
# output. They are ignored by default.
|
|
||||||
#
|
|
||||||
# show_authors = False
|
|
||||||
|
|
||||||
# The name of the Pygments (syntax highlighting) style to use.
|
|
||||||
pygments_style = 'sphinx'
|
|
||||||
|
|
||||||
# A list of ignored prefixes for module index sorting.
|
|
||||||
# modindex_common_prefix = []
|
|
||||||
|
|
||||||
# If true, keep warnings as "system message" paragraphs in the built documents.
|
|
||||||
# keep_warnings = False
|
|
||||||
|
|
||||||
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
|
||||||
todo_include_todos = True
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for HTML output ----------------------------------------------
|
|
||||||
|
|
||||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
|
||||||
# a list of builtin themes.
|
|
||||||
#
|
|
||||||
# html_theme = 'alabaster'
|
|
||||||
|
|
||||||
# Theme options are theme-specific and customize the look and feel of a theme
|
|
||||||
# further. For a list of options available for each theme, see the
|
|
||||||
# documentation.
|
|
||||||
#
|
|
||||||
# html_theme_options = {}
|
|
||||||
|
|
||||||
# Add any paths that contain custom themes here, relative to this directory.
|
|
||||||
# html_theme_path = []
|
|
||||||
|
|
||||||
# The name for this set of Sphinx documents.
|
|
||||||
# "<project> v<release> documentation" by default.
|
|
||||||
#
|
|
||||||
# html_title = u'pyModeS v1.0.5'
|
|
||||||
|
|
||||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
|
||||||
#
|
|
||||||
# html_short_title = None
|
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top
|
|
||||||
# of the sidebar.
|
|
||||||
#
|
|
||||||
# html_logo = None
|
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to use as a favicon of
|
|
||||||
# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
|
||||||
# pixels large.
|
|
||||||
#
|
|
||||||
# html_favicon = None
|
|
||||||
|
|
||||||
# Add any paths that contain custom static files (such as style sheets) here,
|
|
||||||
# relative to this directory. They are copied after the builtin static files,
|
|
||||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
|
||||||
html_static_path = ['_static']
|
|
||||||
|
|
||||||
# Add any extra paths that contain custom files (such as robots.txt or
|
|
||||||
# .htaccess) here, relative to this directory. These files are copied
|
|
||||||
# directly to the root of the documentation.
|
|
||||||
#
|
|
||||||
# html_extra_path = []
|
|
||||||
|
|
||||||
# If not None, a 'Last updated on:' timestamp is inserted at every page
|
|
||||||
# bottom, using the given strftime format.
|
|
||||||
# The empty string is equivalent to '%b %d, %Y'.
|
|
||||||
#
|
|
||||||
# html_last_updated_fmt = None
|
|
||||||
|
|
||||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
|
||||||
# typographically correct entities.
|
|
||||||
#
|
|
||||||
# html_use_smartypants = True
|
|
||||||
|
|
||||||
# Custom sidebar templates, maps document names to template names.
|
|
||||||
#
|
|
||||||
# html_sidebars = {}
|
|
||||||
|
|
||||||
# Additional templates that should be rendered to pages, maps page names to
|
|
||||||
# template names.
|
|
||||||
#
|
|
||||||
# html_additional_pages = {}
|
|
||||||
|
|
||||||
# If false, no module index is generated.
|
|
||||||
#
|
|
||||||
# html_domain_indices = True
|
|
||||||
|
|
||||||
# If false, no index is generated.
|
|
||||||
#
|
|
||||||
# html_use_index = True
|
|
||||||
|
|
||||||
# If true, the index is split into individual pages for each letter.
|
|
||||||
#
|
|
||||||
# html_split_index = False
|
|
||||||
|
|
||||||
# If true, links to the reST sources are added to the pages.
|
|
||||||
#
|
|
||||||
# html_show_sourcelink = True
|
|
||||||
|
|
||||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
|
||||||
#
|
|
||||||
# html_show_sphinx = True
|
|
||||||
|
|
||||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
|
||||||
#
|
|
||||||
# html_show_copyright = True
|
|
||||||
|
|
||||||
# If true, an OpenSearch description file will be output, and all pages will
|
|
||||||
# contain a <link> tag referring to it. The value of this option must be the
|
|
||||||
# base URL from which the finished HTML is served.
|
|
||||||
#
|
|
||||||
# html_use_opensearch = ''
|
|
||||||
|
|
||||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
|
||||||
# html_file_suffix = None
|
|
||||||
|
|
||||||
# Language to be used for generating the HTML full-text search index.
|
|
||||||
# Sphinx supports the following languages:
|
|
||||||
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
|
|
||||||
# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh'
|
|
||||||
#
|
|
||||||
# html_search_language = 'en'
|
|
||||||
|
|
||||||
# A dictionary with options for the search language support, empty by default.
|
|
||||||
# 'ja' uses this config value.
|
|
||||||
# 'zh' user can custom change `jieba` dictionary path.
|
|
||||||
#
|
|
||||||
# html_search_options = {'type': 'default'}
|
|
||||||
|
|
||||||
# The name of a javascript file (relative to the configuration directory) that
|
|
||||||
# implements a search results scorer. If empty, the default will be used.
|
|
||||||
#
|
|
||||||
# html_search_scorer = 'scorer.js'
|
|
||||||
|
|
||||||
# Output file base name for HTML help builder.
|
|
||||||
htmlhelp_basename = 'pyModeSdoc'
|
|
||||||
|
|
||||||
# -- Options for LaTeX output ---------------------------------------------
|
|
||||||
|
|
||||||
latex_elements = {
|
|
||||||
# The paper size ('letterpaper' or 'a4paper').
|
|
||||||
#
|
|
||||||
# 'papersize': 'letterpaper',
|
|
||||||
|
|
||||||
# The font size ('10pt', '11pt' or '12pt').
|
|
||||||
#
|
|
||||||
# 'pointsize': '10pt',
|
|
||||||
|
|
||||||
# Additional stuff for the LaTeX preamble.
|
|
||||||
#
|
|
||||||
# 'preamble': '',
|
|
||||||
|
|
||||||
# Latex figure (float) alignment
|
|
||||||
#
|
|
||||||
# 'figure_align': 'htbp',
|
|
||||||
}
|
|
||||||
|
|
||||||
# Grouping the document tree into LaTeX files. List of tuples
|
|
||||||
# (source start file, target name, title,
|
|
||||||
# author, documentclass [howto, manual, or own class]).
|
|
||||||
latex_documents = [
|
|
||||||
(master_doc, 'pyModeS.tex', u'pyModeS Documentation',
|
|
||||||
u'Junzi Sun', 'manual'),
|
|
||||||
]
|
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top of
|
|
||||||
# the title page.
|
|
||||||
#
|
|
||||||
# latex_logo = None
|
|
||||||
|
|
||||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
|
||||||
# not chapters.
|
|
||||||
#
|
|
||||||
# latex_use_parts = False
|
|
||||||
|
|
||||||
# If true, show page references after internal links.
|
|
||||||
#
|
|
||||||
# latex_show_pagerefs = False
|
|
||||||
|
|
||||||
# If true, show URL addresses after external links.
|
|
||||||
#
|
|
||||||
# latex_show_urls = False
|
|
||||||
|
|
||||||
# Documents to append as an appendix to all manuals.
|
|
||||||
#
|
|
||||||
# latex_appendices = []
|
|
||||||
|
|
||||||
# If false, no module index is generated.
|
|
||||||
#
|
|
||||||
# latex_domain_indices = True
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for manual page output ---------------------------------------
|
|
||||||
|
|
||||||
# One entry per manual page. List of tuples
|
|
||||||
# (source start file, name, description, authors, manual section).
|
|
||||||
man_pages = [
|
|
||||||
(master_doc, 'pymodes', u'pyModeS Documentation',
|
|
||||||
[author], 1)
|
|
||||||
]
|
|
||||||
|
|
||||||
# If true, show URL addresses after external links.
|
|
||||||
#
|
|
||||||
# man_show_urls = False
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for Texinfo output -------------------------------------------
|
|
||||||
|
|
||||||
# Grouping the document tree into Texinfo files. List of tuples
|
|
||||||
# (source start file, target name, title, author,
|
|
||||||
# dir menu entry, description, category)
|
|
||||||
texinfo_documents = [
|
|
||||||
(master_doc, 'pyModeS', u'pyModeS Documentation',
|
|
||||||
author, 'pyModeS', 'One line description of project.',
|
|
||||||
'Miscellaneous'),
|
|
||||||
]
|
|
||||||
|
|
||||||
# Documents to append as an appendix to all manuals.
|
|
||||||
#
|
|
||||||
# texinfo_appendices = []
|
|
||||||
|
|
||||||
# If false, no module index is generated.
|
|
||||||
#
|
|
||||||
# texinfo_domain_indices = True
|
|
||||||
|
|
||||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
|
||||||
#
|
|
||||||
# texinfo_show_urls = 'footnote'
|
|
||||||
|
|
||||||
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
|
||||||
#
|
|
||||||
# texinfo_no_detailmenu = False
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
.. pyModeS documentation master file, created by
|
|
||||||
sphinx-quickstart on Tue Aug 16 15:47:05 2016.
|
|
||||||
You can adapt this file completely to your liking, but it should at least
|
|
||||||
contain the root `toctree` directive.
|
|
||||||
|
|
||||||
|
|
||||||
pyModeS APIs
|
|
||||||
=====================
|
|
||||||
|
|
||||||
This document contains all the functions within pyModeS package.
|
|
||||||
|
|
||||||
Source code and user guide: https://github.com/junzis/pyModeS
|
|
||||||
|
|
||||||
|
|
||||||
pyModeS.adsb module
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
.. automodule:: pyModeS.adsb
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
|
|
||||||
pyModeS.ehs module
|
|
||||||
------------------
|
|
||||||
|
|
||||||
.. automodule:: pyModeS.ehs
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
|
|
||||||
pyModeS.util module
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
.. automodule:: pyModeS.util
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
pyModeS==1.1.0
|
|
||||||
17
doc/source/_templates/layout.html
Normal file
17
doc/source/_templates/layout.html
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{% extends "!layout.html" %}
|
||||||
|
|
||||||
|
{% block footer %}
|
||||||
|
{{ super() }}
|
||||||
|
|
||||||
|
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||||||
|
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-74700456-1"></script>
|
||||||
|
<script>
|
||||||
|
window.dataLayer = window.dataLayer || [];
|
||||||
|
function gtag(){dataLayer.push(arguments);}
|
||||||
|
gtag('js', new Date());
|
||||||
|
|
||||||
|
gtag('config', 'UA-74700456-1');
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
190
doc/source/conf.py
Normal file
190
doc/source/conf.py
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Configuration file for the Sphinx documentation builder.
|
||||||
|
#
|
||||||
|
# This file does only contain a selection of the most common options. For a
|
||||||
|
# full list see the documentation:
|
||||||
|
# http://www.sphinx-doc.org/en/master/config
|
||||||
|
|
||||||
|
# -- Path setup --------------------------------------------------------------
|
||||||
|
|
||||||
|
# If extensions (or modules to document with autodoc) are in another directory,
|
||||||
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
|
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||||
|
#
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.abspath("../.."))
|
||||||
|
|
||||||
|
|
||||||
|
# -- Project information -----------------------------------------------------
|
||||||
|
|
||||||
|
project = "pyModeS"
|
||||||
|
copyright = "2019, Junzi Sun"
|
||||||
|
author = "Junzi Sun"
|
||||||
|
|
||||||
|
# The short X.Y version
|
||||||
|
version = ""
|
||||||
|
# The full version, including alpha/beta/rc tags
|
||||||
|
release = ""
|
||||||
|
|
||||||
|
|
||||||
|
# -- General configuration ---------------------------------------------------
|
||||||
|
|
||||||
|
# If your documentation needs a minimal Sphinx version, state it here.
|
||||||
|
#
|
||||||
|
# needs_sphinx = '1.0'
|
||||||
|
|
||||||
|
# Add any Sphinx extension module names here, as strings. They can be
|
||||||
|
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||||
|
# ones.
|
||||||
|
extensions = [
|
||||||
|
"sphinx.ext.autodoc",
|
||||||
|
"sphinx.ext.mathjax",
|
||||||
|
"sphinx.ext.viewcode",
|
||||||
|
"sphinx.ext.githubpages",
|
||||||
|
"sphinx.ext.napoleon",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
|
templates_path = ["_templates"]
|
||||||
|
|
||||||
|
# The suffix(es) of source filenames.
|
||||||
|
# You can specify multiple suffix as a list of string:
|
||||||
|
#
|
||||||
|
# source_suffix = ['.rst', '.md']
|
||||||
|
source_suffix = ".rst"
|
||||||
|
|
||||||
|
# The master toctree document.
|
||||||
|
master_doc = "index"
|
||||||
|
|
||||||
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
|
# for a list of supported languages.
|
||||||
|
#
|
||||||
|
# This is also used if you do content translation via gettext catalogs.
|
||||||
|
# Usually you set "language" from the command line for these cases.
|
||||||
|
language = None
|
||||||
|
|
||||||
|
# List of patterns, relative to source directory, that match files and
|
||||||
|
# directories to ignore when looking for source files.
|
||||||
|
# This pattern also affects html_static_path and html_extra_path.
|
||||||
|
exclude_patterns = []
|
||||||
|
|
||||||
|
# The name of the Pygments (syntax highlighting) style to use.
|
||||||
|
pygments_style = None
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for HTML output -------------------------------------------------
|
||||||
|
|
||||||
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
|
# a list of builtin themes.
|
||||||
|
#
|
||||||
|
# html_theme = 'alabaster'
|
||||||
|
html_theme = "neo_rtd_theme"
|
||||||
|
import sphinx_theme
|
||||||
|
|
||||||
|
html_theme_path = [sphinx_theme.get_html_theme_path()]
|
||||||
|
|
||||||
|
# Theme options are theme-specific and customize the look and feel of a theme
|
||||||
|
# further. For a list of options available for each theme, see the
|
||||||
|
# documentation.
|
||||||
|
#
|
||||||
|
# html_theme_options = {}
|
||||||
|
|
||||||
|
# Add any paths that contain custom static files (such as style sheets) here,
|
||||||
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||||
|
# html_static_path = ['']
|
||||||
|
|
||||||
|
# Custom sidebar templates, must be a dictionary that maps document names
|
||||||
|
# to template names.
|
||||||
|
#
|
||||||
|
# The default sidebars (for documents that don't match any pattern) are
|
||||||
|
# defined by theme itself. Builtin themes are using these templates by
|
||||||
|
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
|
||||||
|
# 'searchbox.html']``.
|
||||||
|
#
|
||||||
|
# html_sidebars = {}
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for HTMLHelp output ---------------------------------------------
|
||||||
|
|
||||||
|
# Output file base name for HTML help builder.
|
||||||
|
htmlhelp_basename = "pyModeSdoc"
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for LaTeX output ------------------------------------------------
|
||||||
|
|
||||||
|
latex_elements = {
|
||||||
|
# The paper size ('letterpaper' or 'a4paper').
|
||||||
|
#
|
||||||
|
# 'papersize': 'letterpaper',
|
||||||
|
# The font size ('10pt', '11pt' or '12pt').
|
||||||
|
#
|
||||||
|
# 'pointsize': '10pt',
|
||||||
|
# Additional stuff for the LaTeX preamble.
|
||||||
|
#
|
||||||
|
# 'preamble': '',
|
||||||
|
# Latex figure (float) alignment
|
||||||
|
#
|
||||||
|
# 'figure_align': 'htbp',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Grouping the document tree into LaTeX files. List of tuples
|
||||||
|
# (source start file, target name, title,
|
||||||
|
# author, documentclass [howto, manual, or own class]).
|
||||||
|
latex_documents = [
|
||||||
|
(master_doc, "pyModeS.tex", "pyModeS Documentation", "Junzi Sun", "manual")
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for manual page output ------------------------------------------
|
||||||
|
|
||||||
|
# One entry per manual page. List of tuples
|
||||||
|
# (source start file, name, description, authors, manual section).
|
||||||
|
man_pages = [(master_doc, "pymodes", "pyModeS Documentation", [author], 1)]
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for Texinfo output ----------------------------------------------
|
||||||
|
|
||||||
|
# Grouping the document tree into Texinfo files. List of tuples
|
||||||
|
# (source start file, target name, title, author,
|
||||||
|
# dir menu entry, description, category)
|
||||||
|
texinfo_documents = [
|
||||||
|
(
|
||||||
|
master_doc,
|
||||||
|
"pyModeS",
|
||||||
|
"pyModeS Documentation",
|
||||||
|
author,
|
||||||
|
"pyModeS",
|
||||||
|
"One line description of project.",
|
||||||
|
"Miscellaneous",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for Epub output -------------------------------------------------
|
||||||
|
|
||||||
|
# Bibliographic Dublin Core info.
|
||||||
|
epub_title = project
|
||||||
|
|
||||||
|
# The unique identifier of the text. This can be a ISBN number
|
||||||
|
# or the project homepage.
|
||||||
|
#
|
||||||
|
# epub_identifier = ''
|
||||||
|
|
||||||
|
# A unique identification for the text.
|
||||||
|
#
|
||||||
|
# epub_uid = ''
|
||||||
|
|
||||||
|
# A list of files that should not be packed into the epub file.
|
||||||
|
epub_exclude_files = ["search.html"]
|
||||||
|
|
||||||
|
|
||||||
|
# -- Extension configuration -------------------------------------------------
|
||||||
|
|
||||||
|
# -- Options for todo extension ----------------------------------------------
|
||||||
|
|
||||||
|
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||||
|
todo_include_todos = True
|
||||||
68
doc/source/index.rst
Normal file
68
doc/source/index.rst
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
.. pyModeS documentation master file, created by
|
||||||
|
sphinx-quickstart on Mon Apr 1 13:13:10 2019.
|
||||||
|
You can adapt this file completely to your liking, but it should at least
|
||||||
|
contain the root `toctree` directive.
|
||||||
|
|
||||||
|
Welcome to pyModeS documentation!
|
||||||
|
===================================
|
||||||
|
|
||||||
|
The source code can be found at: https://github.com/junzis/pyModeS
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:caption: Core modules
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
pyModeS.decoder.adsb
|
||||||
|
pyModeS.decoder.commb
|
||||||
|
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:caption: ADS-B messages
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
pyModeS.decoder.bds.bds05
|
||||||
|
pyModeS.decoder.bds.bds06
|
||||||
|
pyModeS.decoder.bds.bds08
|
||||||
|
pyModeS.decoder.bds.bds09
|
||||||
|
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:caption: ELS - elementary surveillance
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
pyModeS.decoder.bds.bds10
|
||||||
|
pyModeS.decoder.bds.bds17
|
||||||
|
pyModeS.decoder.bds.bds20
|
||||||
|
pyModeS.decoder.bds.bds30
|
||||||
|
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:caption: EHS - enhanced surveillance
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
pyModeS.decoder.bds.bds40
|
||||||
|
pyModeS.decoder.bds.bds50
|
||||||
|
pyModeS.decoder.bds.bds60
|
||||||
|
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:caption: MRAR / MHR
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
pyModeS.decoder.bds.bds44
|
||||||
|
pyModeS.decoder.bds.bds45
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
.. include:: ../../README.rst
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
Indices and tables
|
||||||
|
**********************
|
||||||
|
|
||||||
|
* :ref:`genindex`
|
||||||
|
* :ref:`modindex`
|
||||||
|
* :ref:`search`
|
||||||
7
doc/source/modules.rst
Normal file
7
doc/source/modules.rst
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
pyModeS
|
||||||
|
=======
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 4
|
||||||
|
|
||||||
|
pyModeS
|
||||||
7
doc/source/pyModeS.c_common.rst
Normal file
7
doc/source/pyModeS.c_common.rst
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
pyModeS.c\_common module
|
||||||
|
========================
|
||||||
|
|
||||||
|
.. automodule:: pyModeS.c_common
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
7
doc/source/pyModeS.common.rst
Normal file
7
doc/source/pyModeS.common.rst
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
pyModeS.common module
|
||||||
|
=====================
|
||||||
|
|
||||||
|
.. automodule:: pyModeS.common
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
7
doc/source/pyModeS.decoder.acas.rst
Normal file
7
doc/source/pyModeS.decoder.acas.rst
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
pyModeS.decoder.acas module
|
||||||
|
===========================
|
||||||
|
|
||||||
|
.. automodule:: pyModeS.decoder.acas
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
7
doc/source/pyModeS.decoder.adsb.rst
Normal file
7
doc/source/pyModeS.decoder.adsb.rst
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
pyModeS.decoder.adsb module
|
||||||
|
===========================
|
||||||
|
|
||||||
|
.. automodule:: pyModeS.decoder.adsb
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
7
doc/source/pyModeS.decoder.allcall.rst
Normal file
7
doc/source/pyModeS.decoder.allcall.rst
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
pyModeS.decoder.allcall module
|
||||||
|
==============================
|
||||||
|
|
||||||
|
.. automodule:: pyModeS.decoder.allcall
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
7
doc/source/pyModeS.decoder.bds.bds05.rst
Normal file
7
doc/source/pyModeS.decoder.bds.bds05.rst
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
pyModeS.decoder.bds.bds05 module
|
||||||
|
================================
|
||||||
|
|
||||||
|
.. automodule:: pyModeS.decoder.bds.bds05
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
7
doc/source/pyModeS.decoder.bds.bds06.rst
Normal file
7
doc/source/pyModeS.decoder.bds.bds06.rst
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
pyModeS.decoder.bds.bds06 module
|
||||||
|
================================
|
||||||
|
|
||||||
|
.. automodule:: pyModeS.decoder.bds.bds06
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
7
doc/source/pyModeS.decoder.bds.bds08.rst
Normal file
7
doc/source/pyModeS.decoder.bds.bds08.rst
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
pyModeS.decoder.bds.bds08 module
|
||||||
|
================================
|
||||||
|
|
||||||
|
.. automodule:: pyModeS.decoder.bds.bds08
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
7
doc/source/pyModeS.decoder.bds.bds09.rst
Normal file
7
doc/source/pyModeS.decoder.bds.bds09.rst
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
pyModeS.decoder.bds.bds09 module
|
||||||
|
================================
|
||||||
|
|
||||||
|
.. automodule:: pyModeS.decoder.bds.bds09
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
7
doc/source/pyModeS.decoder.bds.bds10.rst
Normal file
7
doc/source/pyModeS.decoder.bds.bds10.rst
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
pyModeS.decoder.bds.bds10 module
|
||||||
|
================================
|
||||||
|
|
||||||
|
.. automodule:: pyModeS.decoder.bds.bds10
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
7
doc/source/pyModeS.decoder.bds.bds17.rst
Normal file
7
doc/source/pyModeS.decoder.bds.bds17.rst
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
pyModeS.decoder.bds.bds17 module
|
||||||
|
================================
|
||||||
|
|
||||||
|
.. automodule:: pyModeS.decoder.bds.bds17
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
7
doc/source/pyModeS.decoder.bds.bds20.rst
Normal file
7
doc/source/pyModeS.decoder.bds.bds20.rst
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
pyModeS.decoder.bds.bds20 module
|
||||||
|
================================
|
||||||
|
|
||||||
|
.. automodule:: pyModeS.decoder.bds.bds20
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
7
doc/source/pyModeS.decoder.bds.bds30.rst
Normal file
7
doc/source/pyModeS.decoder.bds.bds30.rst
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
pyModeS.decoder.bds.bds30 module
|
||||||
|
================================
|
||||||
|
|
||||||
|
.. automodule:: pyModeS.decoder.bds.bds30
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
7
doc/source/pyModeS.decoder.bds.bds40.rst
Normal file
7
doc/source/pyModeS.decoder.bds.bds40.rst
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
pyModeS.decoder.bds.bds40 module
|
||||||
|
================================
|
||||||
|
|
||||||
|
.. automodule:: pyModeS.decoder.bds.bds40
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
7
doc/source/pyModeS.decoder.bds.bds44.rst
Normal file
7
doc/source/pyModeS.decoder.bds.bds44.rst
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
pyModeS.decoder.bds.bds44 module
|
||||||
|
================================
|
||||||
|
|
||||||
|
.. automodule:: pyModeS.decoder.bds.bds44
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
7
doc/source/pyModeS.decoder.bds.bds45.rst
Normal file
7
doc/source/pyModeS.decoder.bds.bds45.rst
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
pyModeS.decoder.bds.bds45 module
|
||||||
|
================================
|
||||||
|
|
||||||
|
.. automodule:: pyModeS.decoder.bds.bds45
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
7
doc/source/pyModeS.decoder.bds.bds50.rst
Normal file
7
doc/source/pyModeS.decoder.bds.bds50.rst
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
pyModeS.decoder.bds.bds50 module
|
||||||
|
================================
|
||||||
|
|
||||||
|
.. automodule:: pyModeS.decoder.bds.bds50
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
7
doc/source/pyModeS.decoder.bds.bds53.rst
Normal file
7
doc/source/pyModeS.decoder.bds.bds53.rst
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
pyModeS.decoder.bds.bds53 module
|
||||||
|
================================
|
||||||
|
|
||||||
|
.. automodule:: pyModeS.decoder.bds.bds53
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
7
doc/source/pyModeS.decoder.bds.bds60.rst
Normal file
7
doc/source/pyModeS.decoder.bds.bds60.rst
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
pyModeS.decoder.bds.bds60 module
|
||||||
|
================================
|
||||||
|
|
||||||
|
.. automodule:: pyModeS.decoder.bds.bds60
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
28
doc/source/pyModeS.decoder.bds.rst
Normal file
28
doc/source/pyModeS.decoder.bds.rst
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
pyModeS.decoder.bds package
|
||||||
|
===========================
|
||||||
|
|
||||||
|
.. automodule:: pyModeS.decoder.bds
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
Submodules
|
||||||
|
----------
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 4
|
||||||
|
|
||||||
|
pyModeS.decoder.bds.bds05
|
||||||
|
pyModeS.decoder.bds.bds06
|
||||||
|
pyModeS.decoder.bds.bds08
|
||||||
|
pyModeS.decoder.bds.bds09
|
||||||
|
pyModeS.decoder.bds.bds10
|
||||||
|
pyModeS.decoder.bds.bds17
|
||||||
|
pyModeS.decoder.bds.bds20
|
||||||
|
pyModeS.decoder.bds.bds30
|
||||||
|
pyModeS.decoder.bds.bds40
|
||||||
|
pyModeS.decoder.bds.bds44
|
||||||
|
pyModeS.decoder.bds.bds45
|
||||||
|
pyModeS.decoder.bds.bds50
|
||||||
|
pyModeS.decoder.bds.bds53
|
||||||
|
pyModeS.decoder.bds.bds60
|
||||||
7
doc/source/pyModeS.decoder.commb.rst
Normal file
7
doc/source/pyModeS.decoder.commb.rst
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
pyModeS.decoder.commb module
|
||||||
|
============================
|
||||||
|
|
||||||
|
.. automodule:: pyModeS.decoder.commb
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
29
doc/source/pyModeS.decoder.rst
Normal file
29
doc/source/pyModeS.decoder.rst
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
pyModeS.decoder package
|
||||||
|
=======================
|
||||||
|
|
||||||
|
.. automodule:: pyModeS.decoder
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
Subpackages
|
||||||
|
-----------
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 4
|
||||||
|
|
||||||
|
pyModeS.decoder.bds
|
||||||
|
|
||||||
|
Submodules
|
||||||
|
----------
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 4
|
||||||
|
|
||||||
|
pyModeS.decoder.acas
|
||||||
|
pyModeS.decoder.adsb
|
||||||
|
pyModeS.decoder.allcall
|
||||||
|
pyModeS.decoder.commb
|
||||||
|
pyModeS.decoder.surv
|
||||||
|
pyModeS.decoder.uncertainty
|
||||||
|
pyModeS.decoder.uplink
|
||||||
7
doc/source/pyModeS.decoder.surv.rst
Normal file
7
doc/source/pyModeS.decoder.surv.rst
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
pyModeS.decoder.surv module
|
||||||
|
===========================
|
||||||
|
|
||||||
|
.. automodule:: pyModeS.decoder.surv
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
7
doc/source/pyModeS.decoder.uncertainty.rst
Normal file
7
doc/source/pyModeS.decoder.uncertainty.rst
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
pyModeS.decoder.uncertainty module
|
||||||
|
==================================
|
||||||
|
|
||||||
|
.. automodule:: pyModeS.decoder.uncertainty
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
7
doc/source/pyModeS.decoder.uplink.rst
Normal file
7
doc/source/pyModeS.decoder.uplink.rst
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
pyModeS.decoder.uplink module
|
||||||
|
=============================
|
||||||
|
|
||||||
|
.. automodule:: pyModeS.decoder.uplink
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
24
doc/source/pyModeS.rst
Normal file
24
doc/source/pyModeS.rst
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
pyModeS package
|
||||||
|
===============
|
||||||
|
|
||||||
|
.. automodule:: pyModeS
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
Subpackages
|
||||||
|
-----------
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 4
|
||||||
|
|
||||||
|
pyModeS.decoder
|
||||||
|
|
||||||
|
Submodules
|
||||||
|
----------
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 4
|
||||||
|
|
||||||
|
pyModeS.c_common
|
||||||
|
pyModeS.common
|
||||||
0
doc/warnings
Normal file
0
doc/warnings
Normal file
@@ -1,15 +1,21 @@
|
|||||||
from __future__ import absolute_import, print_function, division
|
import os
|
||||||
|
import warnings
|
||||||
|
|
||||||
from .decoder.common import *
|
try:
|
||||||
|
from . import c_common as common
|
||||||
|
from .c_common import *
|
||||||
|
except:
|
||||||
|
from . import common
|
||||||
|
from .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
|
|
||||||
# from .decoder import ehs # depricated
|
|
||||||
|
|
||||||
import os
|
warnings.simplefilter("once", DeprecationWarning)
|
||||||
|
|
||||||
dirpath = os.path.dirname(os.path.realpath(__file__))
|
dirpath = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
|||||||
23
pyModeS/c_common.pxd
Normal file
23
pyModeS/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)
|
||||||
|
|
||||||
|
cpdef str data(str msg)
|
||||||
|
cpdef bint allzeros(str msg)
|
||||||
429
pyModeS/c_common.pyx
Normal file
429
pyModeS/c_common.pyx
Normal file
@@ -0,0 +1,429 @@
|
|||||||
|
# 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 abs, 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 abs(lat) <= 1e-08:
|
||||||
|
return 59
|
||||||
|
elif abs(abs(lat) - 87) <= 1e-08 + 1e-05 * 87:
|
||||||
|
return 2
|
||||||
|
elif 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
|
||||||
|
|
||||||
|
|
||||||
|
cpdef 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
|
||||||
359
pyModeS/common.py
Normal file
359
pyModeS/common.py
Normal file
@@ -0,0 +1,359 @@
|
|||||||
|
import numpy as np
|
||||||
|
from textwrap import wrap
|
||||||
|
|
||||||
|
|
||||||
|
def hex2bin(hexstr):
|
||||||
|
"""Convert a hexdecimal string to binary string, with zero fillings."""
|
||||||
|
num_of_bits = len(hexstr) * 4
|
||||||
|
binstr = bin(int(hexstr, 16))[2:].zfill(int(num_of_bits))
|
||||||
|
return binstr
|
||||||
|
|
||||||
|
|
||||||
|
def hex2int(hexstr):
|
||||||
|
"""Convert a hexdecimal string to integer."""
|
||||||
|
return int(hexstr, 16)
|
||||||
|
|
||||||
|
|
||||||
|
def bin2int(binstr):
|
||||||
|
"""Convert a binary string to integer."""
|
||||||
|
return int(binstr, 2)
|
||||||
|
|
||||||
|
|
||||||
|
def df(msg):
|
||||||
|
"""Decode Downlink Format value, bits 1 to 5."""
|
||||||
|
dfbin = hex2bin(msg[:2])
|
||||||
|
return min(bin2int(dfbin[0:5]), 24)
|
||||||
|
|
||||||
|
|
||||||
|
def crc(msg, 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)]
|
||||||
|
|
||||||
|
if encode:
|
||||||
|
msg = msg[:-6] + "000000"
|
||||||
|
|
||||||
|
msgbin = hex2bin(msg)
|
||||||
|
msgbin_split = wrap(msgbin, 8)
|
||||||
|
mbytes = list(map(bin2int, msgbin_split))
|
||||||
|
|
||||||
|
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))
|
||||||
|
)
|
||||||
|
|
||||||
|
result = (mbytes[-3] << 16) | (mbytes[-2] << 8) | mbytes[-1]
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def crc_legacy(msg, encode=False):
|
||||||
|
"""Mode-S Cyclic Redundancy Check. (Legacy code, 2x slow)."""
|
||||||
|
# the polynominal generattor code for CRC [1111111111111010000001001]
|
||||||
|
generator = np.array(
|
||||||
|
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1]
|
||||||
|
)
|
||||||
|
ng = len(generator)
|
||||||
|
|
||||||
|
msgnpbin = np.array([int(i) for i in hex2bin(msg)])
|
||||||
|
|
||||||
|
if encode:
|
||||||
|
msgnpbin[-24:] = [0] * 24
|
||||||
|
|
||||||
|
# loop all bits, except last 24 piraty bits
|
||||||
|
for i in range(len(msgnpbin) - 24):
|
||||||
|
if msgnpbin[i] == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# perform XOR, when 1
|
||||||
|
msgnpbin[i : i + ng] = np.bitwise_xor(msgnpbin[i : i + ng], generator)
|
||||||
|
|
||||||
|
# last 24 bits
|
||||||
|
msgbin = np.array2string(msgnpbin[-24:], separator="")[1:-1]
|
||||||
|
reminder = bin2int(msgbin)
|
||||||
|
|
||||||
|
return reminder
|
||||||
|
|
||||||
|
|
||||||
|
def floor(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 int(np.floor(x))
|
||||||
|
|
||||||
|
|
||||||
|
def icao(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
|
||||||
|
|
||||||
|
"""
|
||||||
|
DF = df(msg)
|
||||||
|
|
||||||
|
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 = int(msg[-6:], 16)
|
||||||
|
addr = "%06X" % (c0 ^ c1)
|
||||||
|
else:
|
||||||
|
addr = None
|
||||||
|
|
||||||
|
return addr
|
||||||
|
|
||||||
|
|
||||||
|
def is_icao_assigned(icao):
|
||||||
|
"""Check whether the ICAO address is assigned (Annex 10, Vol 3)."""
|
||||||
|
if (icao is None) or (not isinstance(icao, str)) or (len(icao) != 6):
|
||||||
|
return False
|
||||||
|
|
||||||
|
icaoint = int(icao, 16)
|
||||||
|
|
||||||
|
if 0x200000 < icaoint < 0x27FFFF:
|
||||||
|
return False # AFI
|
||||||
|
if 0x280000 < icaoint < 0x28FFFF:
|
||||||
|
return False # SAM
|
||||||
|
if 0x500000 < icaoint < 0x5FFFFF:
|
||||||
|
return False # EUR, NAT
|
||||||
|
if 0x600000 < icaoint < 0x67FFFF:
|
||||||
|
return False # MID
|
||||||
|
if 0x680000 < icaoint < 0x6F0000:
|
||||||
|
return False # ASIA
|
||||||
|
if 0x900000 < icaoint < 0x9FFFFF:
|
||||||
|
return False # NAM, PAC
|
||||||
|
if 0xB00000 < icaoint < 0xBFFFFF:
|
||||||
|
return False # CAR
|
||||||
|
if 0xD00000 < icaoint < 0xDFFFFF:
|
||||||
|
return False # future
|
||||||
|
if 0xF00000 < icaoint < 0xFFFFFF:
|
||||||
|
return False # future
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def typecode(msg):
|
||||||
|
"""Type code of ADS-B message
|
||||||
|
|
||||||
|
Args:
|
||||||
|
msg (string): 28 bytes hexadecimal message string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: type code number
|
||||||
|
"""
|
||||||
|
if df(msg) not in (17, 18):
|
||||||
|
return None
|
||||||
|
|
||||||
|
tcbin = hex2bin(msg[8:10])
|
||||||
|
return bin2int(tcbin[0:5])
|
||||||
|
|
||||||
|
|
||||||
|
def cprNL(lat):
|
||||||
|
"""NL() function in CPR decoding."""
|
||||||
|
|
||||||
|
if np.isclose(lat, 0):
|
||||||
|
return 59
|
||||||
|
elif np.isclose(abs(lat), 87):
|
||||||
|
return 2
|
||||||
|
elif lat > 87 or lat < -87:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
nz = 15
|
||||||
|
a = 1 - np.cos(np.pi / (2 * nz))
|
||||||
|
b = np.cos(np.pi / 180.0 * abs(lat)) ** 2
|
||||||
|
nl = 2 * np.pi / (np.arccos(1 - a / b))
|
||||||
|
NL = floor(nl)
|
||||||
|
return NL
|
||||||
|
|
||||||
|
|
||||||
|
def idcode(msg):
|
||||||
|
"""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.")
|
||||||
|
|
||||||
|
mbin = hex2bin(msg)
|
||||||
|
|
||||||
|
C1 = mbin[19]
|
||||||
|
A1 = mbin[20]
|
||||||
|
C2 = mbin[21]
|
||||||
|
A2 = mbin[22]
|
||||||
|
C4 = mbin[23]
|
||||||
|
A4 = mbin[24]
|
||||||
|
# _ = mbin[25]
|
||||||
|
B1 = mbin[26]
|
||||||
|
D1 = mbin[27]
|
||||||
|
B2 = mbin[28]
|
||||||
|
D2 = mbin[29]
|
||||||
|
B4 = mbin[30]
|
||||||
|
D4 = mbin[31]
|
||||||
|
|
||||||
|
byte1 = int(A4 + A2 + A1, 2)
|
||||||
|
byte2 = int(B4 + B2 + B1, 2)
|
||||||
|
byte3 = int(C4 + C2 + C1, 2)
|
||||||
|
byte4 = int(D4 + D2 + D1, 2)
|
||||||
|
|
||||||
|
return str(byte1) + str(byte2) + str(byte3) + str(byte4)
|
||||||
|
|
||||||
|
|
||||||
|
def altcode(msg):
|
||||||
|
"""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
|
||||||
|
mbin = hex2bin(msg)
|
||||||
|
|
||||||
|
mbit = mbin[25] # M bit: 26
|
||||||
|
qbit = mbin[27] # Q bit: 28
|
||||||
|
|
||||||
|
if mbit == "0": # unit in ft
|
||||||
|
if qbit == "1": # 25ft interval
|
||||||
|
vbin = mbin[19:25] + mbin[26] + mbin[28:32]
|
||||||
|
alt = bin2int(vbin) * 25 - 1000
|
||||||
|
if qbit == "0": # 100ft interval, above 50175ft
|
||||||
|
C1 = mbin[19]
|
||||||
|
A1 = mbin[20]
|
||||||
|
C2 = mbin[21]
|
||||||
|
A2 = mbin[22]
|
||||||
|
C4 = mbin[23]
|
||||||
|
A4 = mbin[24]
|
||||||
|
# _ = mbin[25]
|
||||||
|
B1 = mbin[26]
|
||||||
|
# D1 = mbin[27] # always zero
|
||||||
|
B2 = mbin[28]
|
||||||
|
D2 = mbin[29]
|
||||||
|
B4 = mbin[30]
|
||||||
|
D4 = mbin[31]
|
||||||
|
|
||||||
|
graystr = D2 + D4 + A1 + A2 + A4 + B1 + B2 + B4 + C1 + C2 + C4
|
||||||
|
alt = gray2alt(graystr)
|
||||||
|
|
||||||
|
if mbit == "1": # unit in meter
|
||||||
|
vbin = mbin[19:25] + mbin[26:31]
|
||||||
|
alt = int(bin2int(vbin) * 3.28084) # convert to ft
|
||||||
|
|
||||||
|
return alt
|
||||||
|
|
||||||
|
|
||||||
|
def gray2alt(codestr):
|
||||||
|
gc500 = codestr[:8]
|
||||||
|
n500 = gray2int(gc500)
|
||||||
|
|
||||||
|
# in 100-ft step must be converted first
|
||||||
|
gc100 = codestr[8:]
|
||||||
|
n100 = gray2int(gc100)
|
||||||
|
|
||||||
|
if n100 in [0, 5, 6]:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if n100 == 7:
|
||||||
|
n100 = 5
|
||||||
|
|
||||||
|
if n500 % 2:
|
||||||
|
n100 = 6 - n100
|
||||||
|
|
||||||
|
alt = (n500 * 500 + n100 * 100) - 1300
|
||||||
|
return alt
|
||||||
|
|
||||||
|
|
||||||
|
def gray2int(graystr):
|
||||||
|
"""Convert greycode to binary."""
|
||||||
|
num = bin2int(graystr)
|
||||||
|
num ^= num >> 8
|
||||||
|
num ^= num >> 4
|
||||||
|
num ^= num >> 2
|
||||||
|
num ^= num >> 1
|
||||||
|
return num
|
||||||
|
|
||||||
|
|
||||||
|
def data(msg):
|
||||||
|
"""Return the data frame in the message, bytes 9 to 22."""
|
||||||
|
return msg[8:-6]
|
||||||
|
|
||||||
|
|
||||||
|
def allzeros(msg):
|
||||||
|
"""Check if the data bits are all zeros.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
msg (String): 28 bytes hexadecimal message string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True or False
|
||||||
|
|
||||||
|
"""
|
||||||
|
d = hex2bin(data(msg))
|
||||||
|
|
||||||
|
if bin2int(d) > 0:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def wrongstatus(data, sb, msb, lsb):
|
||||||
|
"""Check if the status bit and field bits are consistency.
|
||||||
|
|
||||||
|
This Function is used for checking BDS code versions.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# status bit, most significant bit, least significant bit
|
||||||
|
status = int(data[sb - 1])
|
||||||
|
value = bin2int(data[msb - 1 : lsb])
|
||||||
|
|
||||||
|
if not status:
|
||||||
|
if value != 0:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
@@ -1,3 +1,142 @@
|
|||||||
from __future__ import absolute_import, print_function, division
|
def tell(msg):
|
||||||
|
from pyModeS import common, adsb, commb, bds
|
||||||
|
|
||||||
from pyModeS.decoder import *
|
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("Protocol", "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 position")
|
||||||
|
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("Protocol", "Mode-S Comm-B altitude reply")
|
||||||
|
_print("Altitude", common.altcode(msg), "feet")
|
||||||
|
|
||||||
|
if df == 21:
|
||||||
|
_print("Protocol", "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,21 +1,5 @@
|
|||||||
# 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
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, print_function, division
|
[To be implemented]
|
||||||
from pyModeS.decoder import common
|
"""
|
||||||
|
|||||||
@@ -1,89 +1,101 @@
|
|||||||
# Copyright (C) 2015 Junzi Sun (TU Delft)
|
"""ADS-B module.
|
||||||
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
The ADS-B module also imports functions from the following modules:
|
||||||
# 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,
|
- pyModeS.decoder.bds.bds05: ``airborne_position()``, ``airborne_position_with_ref()``, ``altitude()``
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
- pyModeS.decoder.bds.bds06: ``surface_position()``, ``surface_position_with_ref()``, ``surface_velocity()``
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
- pyModeS.decoder.bds.bds08: ``category()``, ``callsign()``
|
||||||
# GNU General Public License for more details.
|
- pyModeS.decoder.bds.bds09: ``airborne_velocity()``, ``altitude_diff()``
|
||||||
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
The wrapper for decoding ADS-B messages
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, print_function, division
|
|
||||||
|
|
||||||
import pyModeS as pms
|
import pyModeS as pms
|
||||||
from pyModeS.decoder import common
|
|
||||||
|
from pyModeS 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 surface or airborne position from a pair of even and odd
|
||||||
(works with both airborne and surface position messages)
|
position messages.
|
||||||
|
|
||||||
|
Note, that to decode surface position using the position message pair,
|
||||||
|
the reference position has to be provided.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg0 (string): even message (28 bytes hexadecimal string)
|
msg0 (string): even message (28 hexdigits)
|
||||||
msg1 (string): odd message (28 bytes hexadecimal string)
|
msg1 (string): odd message (28 hexdigits)
|
||||||
t0 (int): timestamps for the even message
|
t0 (int): timestamps for the even message
|
||||||
t1 (int): timestamps for the odd message
|
t1 (int): timestamps for the odd message
|
||||||
|
lat_ref (float): latitude of reference position
|
||||||
|
lon_ref (float): longitude of reference position
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
(float, float): (latitude, longitude) of the aircraft
|
(float, float): (latitude, longitude) of the aircraft
|
||||||
|
|
||||||
"""
|
"""
|
||||||
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 lat_ref is None or lon_ref is None:
|
||||||
raise RuntimeError("Surface position encountered, a reference \
|
raise RuntimeError(
|
||||||
position lat/lon required. Location of \
|
"Surface position encountered, a reference position"
|
||||||
receiver can be used.")
|
" lat/lon required. Location of 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)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise RuntimeError("incorrect or inconsistant message types")
|
raise RuntimeError("Incorrect or inconsistent message types")
|
||||||
|
|
||||||
|
|
||||||
def position_with_ref(msg, lat_ref, lon_ref):
|
def position_with_ref(msg, lat_ref, lon_ref):
|
||||||
"""Decode position with only one message,
|
"""Decode position with only one message.
|
||||||
knowing reference nearby location, such as previously
|
|
||||||
calculated location, ground station, or airport location, etc.
|
A reference position is required, which can be previously
|
||||||
Works with both airborne and surface position messages.
|
calculated location, ground station, or airport location.
|
||||||
|
The function works with both airborne and surface position messages.
|
||||||
The reference position shall be with in 180NM (airborne) or 45NM (surface)
|
The reference position shall be with in 180NM (airborne) or 45NM (surface)
|
||||||
of the true position.
|
of the true position.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (string): even message (28 bytes hexadecimal string)
|
msg (str): even message (28 hexdigits)
|
||||||
lat_ref: previous known latitude
|
lat_ref: previous known latitude
|
||||||
lon_ref: previous known longitude
|
lon_ref: previous known longitude
|
||||||
|
|
||||||
@@ -93,66 +105,70 @@ 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:
|
||||||
raise RuntimeError("incorrect or inconsistant message types")
|
raise RuntimeError("incorrect or inconsistent message types")
|
||||||
|
|
||||||
|
|
||||||
def altitude(msg):
|
def altitude(msg):
|
||||||
"""Decode aircraft altitude
|
"""Decode aircraft altitude.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (string): 28 bytes hexadecimal message string
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
int: altitude in feet
|
int: altitude in feet
|
||||||
"""
|
|
||||||
|
|
||||||
|
"""
|
||||||
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):
|
def velocity(msg, source=False):
|
||||||
"""Calculate the speed, heading, and vertical rate
|
"""Calculate the speed, heading, and vertical rate (handles both airborne or surface message).
|
||||||
(handles both airborne or surface message)
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (string): 28 bytes hexadecimal message string
|
msg (str): 28 hexdigits string
|
||||||
|
source (boolean): Include direction and vertical rate sources in return. Default to False.
|
||||||
|
If set to True, the function will return six value instead of four.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
(int, float, int, string): speed (kt), ground track or heading (degree),
|
int, float, int, string, [string], [string]: Four or six parameters, including:
|
||||||
rate of climb/descend (ft/min), and speed type
|
- Speed (kt)
|
||||||
('GS' for ground speed, 'AS' for airspeed)
|
- Angle (degree), either ground track or heading
|
||||||
"""
|
- Vertical rate (ft/min)
|
||||||
|
- Speed type ('GS' for ground speed, 'AS' for airspeed)
|
||||||
|
- [Optional] Direction source ('TRUE_NORTH' or 'MAGENTIC_NORTH')
|
||||||
|
- [Optional] Vertical rate source ('BARO' or 'GNSS')
|
||||||
|
|
||||||
|
For surface messages, vertical rate and its respective sources are set to None.
|
||||||
|
|
||||||
|
"""
|
||||||
if 5 <= typecode(msg) <= 8:
|
if 5 <= typecode(msg) <= 8:
|
||||||
return surface_velocity(msg)
|
return surface_velocity(msg, source)
|
||||||
|
|
||||||
elif typecode(msg) == 19:
|
elif typecode(msg) == 19:
|
||||||
return airborne_velocity(msg)
|
return airborne_velocity(msg, source)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise RuntimeError("incorrect or inconsistant message types, expecting 4<TC<9 or TC=19")
|
raise RuntimeError(
|
||||||
|
"incorrect or inconsistent message types, expecting 4<TC<9 or TC=19"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def speed_heading(msg):
|
def speed_heading(msg):
|
||||||
@@ -160,7 +176,7 @@ def speed_heading(msg):
|
|||||||
(handles both airborne or surface message)
|
(handles both airborne or surface message)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (string): 28 bytes hexadecimal message string
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
(int, float): speed (kt), ground track or heading (degree)
|
(int, float): speed (kt), ground track or heading (degree)
|
||||||
@@ -172,7 +188,7 @@ def speed_heading(msg):
|
|||||||
def oe_flag(msg):
|
def oe_flag(msg):
|
||||||
"""Check the odd/even flag. Bit 54, 0 for even, 1 for odd.
|
"""Check the odd/even flag. Bit 54, 0 for even, 1 for odd.
|
||||||
Args:
|
Args:
|
||||||
msg (string): 28 bytes hexadecimal message string
|
msg (str): 28 hexdigits string
|
||||||
Returns:
|
Returns:
|
||||||
int: 0 or 1, for even or odd frame
|
int: 0 or 1, for even or odd frame
|
||||||
"""
|
"""
|
||||||
@@ -184,7 +200,7 @@ def version(msg):
|
|||||||
"""ADS-B Version
|
"""ADS-B Version
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (string): 28 bytes hexadecimal message string, TC = 31
|
msg (str): 28 hexdigits string, TC = 31
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
int: version number
|
int: version number
|
||||||
@@ -192,7 +208,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])
|
||||||
@@ -204,7 +222,7 @@ def nuc_p(msg):
|
|||||||
"""Calculate NUCp, Navigation Uncertainty Category - Position (ADS-B version 1)
|
"""Calculate NUCp, Navigation Uncertainty Category - Position (ADS-B version 1)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (string): 28 bytes hexadecimal message string,
|
msg (str): 28 hexdigits string,
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
int: Horizontal Protection Limit
|
int: Horizontal Protection Limit
|
||||||
@@ -218,18 +236,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
|
||||||
|
|
||||||
@@ -240,7 +258,7 @@ def nuc_v(msg):
|
|||||||
"""Calculate NUCv, Navigation Uncertainty Category - Velocity (ADS-B version 1)
|
"""Calculate NUCv, Navigation Uncertainty Category - Velocity (ADS-B version 1)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (string): 28 bytes hexadecimal message string,
|
msg (str): 28 hexdigits string,
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
int or string: 95% Horizontal Velocity Error
|
int or string: 95% Horizontal Velocity Error
|
||||||
@@ -249,15 +267,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
|
||||||
|
|
||||||
@@ -268,7 +287,7 @@ def nic_v1(msg, NICs):
|
|||||||
"""Calculate NIC, navigation integrity category, for ADS-B version 1
|
"""Calculate NIC, navigation integrity category, for ADS-B version 1
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (string): 28 bytes hexadecimal message string
|
msg (str): 28 hexdigits string
|
||||||
NICs (int or string): NIC supplement
|
NICs (int or string): NIC supplement
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -279,7 +298,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)
|
||||||
@@ -289,8 +309,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
|
||||||
|
|
||||||
@@ -301,7 +321,7 @@ def nic_v2(msg, NICa, NICbc):
|
|||||||
"""Calculate NIC, navigation integrity category, for ADS-B version 2
|
"""Calculate NIC, navigation integrity category, for ADS-B version 2
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (string): 28 bytes hexadecimal message string
|
msg (str): 28 hexdigits string
|
||||||
NICa (int or string): NIC supplement - A
|
NICa (int or string): NIC supplement - A
|
||||||
NICbc (int or srting): NIC supplement - B or C
|
NICbc (int or srting): NIC supplement - B or C
|
||||||
|
|
||||||
@@ -312,22 +332,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
|
||||||
|
|
||||||
@@ -338,7 +359,7 @@ def nic_s(msg):
|
|||||||
"""Obtain NIC supplement bit, TC=31 message
|
"""Obtain NIC supplement bit, TC=31 message
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (string): 28 bytes hexadecimal message string
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
int: NICs number (0 or 1)
|
int: NICs number (0 or 1)
|
||||||
@@ -346,7 +367,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])
|
||||||
@@ -358,7 +381,7 @@ def nic_a_c(msg):
|
|||||||
"""Obtain NICa/c, navigation integrity category supplements a and c
|
"""Obtain NICa/c, navigation integrity category supplements a and c
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (string): 28 bytes hexadecimal message string
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
(int, int): NICa and NICc number (0 or 1)
|
(int, int): NICa and NICc number (0 or 1)
|
||||||
@@ -366,7 +389,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])
|
||||||
@@ -379,7 +404,7 @@ def nic_b(msg):
|
|||||||
"""Obtain NICb, navigation integrity category supplement-b
|
"""Obtain NICb, navigation integrity category supplement-b
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (string): 28 bytes hexadecimal message string
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
int: NICb number (0 or 1)
|
int: NICb number (0 or 1)
|
||||||
@@ -387,7 +412,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])
|
||||||
@@ -399,7 +426,7 @@ def nac_p(msg):
|
|||||||
"""Calculate NACp, Navigation Accuracy Category - Position
|
"""Calculate NACp, Navigation Accuracy Category - Position
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (string): 28 bytes hexadecimal message string, TC = 29 or 31
|
msg (str): 28 hexdigits string, TC = 29 or 31
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
int or string: 95% horizontal accuracy bounds, Estimated Position Uncertainty
|
int or string: 95% horizontal accuracy bounds, Estimated Position Uncertainty
|
||||||
@@ -408,8 +435,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)
|
||||||
|
|
||||||
@@ -419,8 +449,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
|
||||||
|
|
||||||
@@ -431,7 +461,7 @@ def nac_v(msg):
|
|||||||
"""Calculate NACv, Navigation Accuracy Category - Velocity
|
"""Calculate NACv, Navigation Accuracy Category - Velocity
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (string): 28 bytes hexadecimal message string, TC = 19
|
msg (str): 28 hexdigits string, TC = 19
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
int or string: 95% horizontal accuracy bounds for velocity, Horizontal Figure of Merit
|
int or string: 95% horizontal accuracy bounds for velocity, Horizontal Figure of Merit
|
||||||
@@ -440,14 +470,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
|
||||||
|
|
||||||
@@ -458,7 +490,7 @@ def sil(msg, version):
|
|||||||
"""Calculate SIL, Surveillance Integrity Level
|
"""Calculate SIL, Surveillance Integrity Level
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (string): 28 bytes hexadecimal message string with TC = 29, 31
|
msg (str): 28 hexdigits string with TC = 29, 31
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
int or string: Probability of exceeding Horizontal Radius of Containment RCu
|
int or string: Probability of exceeding Horizontal Radius of Containment RCu
|
||||||
@@ -468,8 +500,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 message, \
|
||||||
|
or operation status message, expecting TC = 29 or 31"
|
||||||
|
% msg
|
||||||
|
)
|
||||||
|
|
||||||
msgbin = common.hex2bin(msg)
|
msgbin = common.hex2bin(msg)
|
||||||
|
|
||||||
@@ -479,12 +514,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,21 +1,5 @@
|
|||||||
# 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
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, print_function, division
|
[To be implemented]
|
||||||
from pyModeS.decoder import common
|
"""
|
||||||
|
|||||||
@@ -18,27 +18,43 @@
|
|||||||
Common functions for Mode-S decoding
|
Common functions for Mode-S decoding
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import absolute_import, print_function, division
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from pyModeS.extra import aero
|
from pyModeS.extra import aero
|
||||||
from pyModeS.decoder import common
|
from pyModeS import common
|
||||||
from pyModeS.decoder.bds import bds05, bds06, bds08, bds09, \
|
|
||||||
bds10, bds17, bds20, bds30, bds40, bds44, bds50, bds53, bds60
|
from pyModeS.decoder.bds import (
|
||||||
|
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):
|
||||||
"""Use reference ground speed and trk to determine BDS50 and DBS60
|
"""Use reference ground speed and trk to determine BDS50 and DBS60.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (String): 28 bytes hexadecimal message string
|
msg (str): 28 hexdigits string
|
||||||
spd_ref (float): reference speed (ADS-B ground speed), kts
|
spd_ref (float): reference speed (ADS-B ground speed), kts
|
||||||
trk_ref (float): reference track (ADS-B track angle), deg
|
trk_ref (float): reference track (ADS-B track angle), deg
|
||||||
alt_ref (float): reference altitude (ADS-B altitude), ft
|
alt_ref (float): reference altitude (ADS-B altitude), ft
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
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))
|
||||||
@@ -51,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]]
|
||||||
@@ -80,51 +96,52 @@ 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
|
||||||
|
|
||||||
|
|
||||||
def infer(msg):
|
def infer(msg, mrar=False):
|
||||||
"""Estimate the most likely BDS code of an message
|
"""Estimate the most likely BDS code of an message.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (String): 28 bytes hexadecimal message string
|
msg (str): 28 hexdigits string
|
||||||
|
mrar (bool): Also infer MRAR (BDS 44) and MHR (BDS 45). Defaults to False.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
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.
|
||||||
"""
|
|
||||||
|
|
||||||
|
"""
|
||||||
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" # identification 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, ELS + EHS only
|
# For Comm-B replies
|
||||||
IS10 = bds10.is10(msg)
|
IS10 = bds10.is10(msg)
|
||||||
IS17 = bds17.is17(msg)
|
IS17 = bds17.is17(msg)
|
||||||
IS20 = bds20.is20(msg)
|
IS20 = bds20.is20(msg)
|
||||||
@@ -132,14 +149,31 @@ def infer(msg):
|
|||||||
IS40 = bds40.is40(msg)
|
IS40 = bds40.is40(msg)
|
||||||
IS50 = bds50.is50(msg)
|
IS50 = bds50.is50(msg)
|
||||||
IS60 = bds60.is60(msg)
|
IS60 = bds60.is60(msg)
|
||||||
|
IS44 = bds44.is44(msg)
|
||||||
|
IS45 = bds45.is45(msg)
|
||||||
|
|
||||||
allbds = np.array([
|
if mrar:
|
||||||
"BDS10", "BDS17", "BDS20", "BDS30", "BDS40", "BDS50", "BDS60"
|
allbds = np.array(
|
||||||
])
|
[
|
||||||
|
"BDS10",
|
||||||
|
"BDS17",
|
||||||
|
"BDS20",
|
||||||
|
"BDS30",
|
||||||
|
"BDS40",
|
||||||
|
"BDS44",
|
||||||
|
"BDS45",
|
||||||
|
"BDS50",
|
||||||
|
"BDS60",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
mask = [IS10, IS17, IS20, IS30, IS40, IS44, IS45, IS50, IS60]
|
||||||
|
else:
|
||||||
|
allbds = np.array(
|
||||||
|
["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,36 +1,18 @@
|
|||||||
# Copyright (C) 2018 Junzi Sun (TU Delft)
|
# ------------------------------------------
|
||||||
|
# BDS 0,5
|
||||||
|
# ADS-B TC=9-18
|
||||||
|
# Airborn position
|
||||||
|
# ------------------------------------------
|
||||||
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
from pyModeS import common
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
------------------------------------------
|
|
||||||
BDS 0,5
|
|
||||||
ADS-B TC=9-18
|
|
||||||
Airborn position
|
|
||||||
------------------------------------------
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, print_function, division
|
|
||||||
from pyModeS.decoder import common
|
|
||||||
|
|
||||||
def airborne_position(msg0, msg1, t0, t1):
|
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
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg0 (string): even message (28 bytes hexadecimal string)
|
msg0 (string): even message (28 hexdigits)
|
||||||
msg1 (string): odd message (28 bytes hexadecimal string)
|
msg1 (string): odd message (28 hexdigits)
|
||||||
t0 (int): timestamps for the even message
|
t0 (int): timestamps for the even message
|
||||||
t1 (int): timestamps for the odd message
|
t1 (int): timestamps for the odd message
|
||||||
|
|
||||||
@@ -41,6 +23,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
|
||||||
@@ -67,17 +59,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:
|
||||||
@@ -93,7 +85,7 @@ def airborne_position_with_ref(msg, lat_ref, lon_ref):
|
|||||||
be with in 180NM of the true position.
|
be with in 180NM of the true position.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (string): even message (28 bytes hexadecimal string)
|
msg (str): even message (28 hexdigits)
|
||||||
lat_ref: previous known latitude
|
lat_ref: previous known latitude
|
||||||
lon_ref: previous known longitude
|
lon_ref: previous known longitude
|
||||||
|
|
||||||
@@ -101,17 +93,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)
|
||||||
|
|
||||||
@@ -122,8 +114,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)
|
||||||
|
|
||||||
@@ -134,7 +127,7 @@ def altitude(msg):
|
|||||||
"""Decode aircraft altitude
|
"""Decode aircraft altitude
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (string): 28 bytes hexadecimal message string
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
int: altitude in feet
|
int: altitude in feet
|
||||||
@@ -142,7 +135,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:]
|
||||||
@@ -151,7 +144,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,30 +1,10 @@
|
|||||||
# Copyright (C) 2018 Junzi Sun (TU Delft)
|
# ------------------------------------------
|
||||||
|
# BDS 0,6
|
||||||
|
# ADS-B TC=5-8
|
||||||
|
# Surface movment
|
||||||
|
# ------------------------------------------
|
||||||
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
from pyModeS import common
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
------------------------------------------
|
|
||||||
BDS 0,6
|
|
||||||
ADS-B TC=5-8
|
|
||||||
Surface position
|
|
||||||
------------------------------------------
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, print_function, division
|
|
||||||
from pyModeS.decoder import common
|
|
||||||
import math
|
|
||||||
|
|
||||||
|
|
||||||
def surface_position(msg0, msg1, t0, t1, lat_ref, lon_ref):
|
def surface_position(msg0, msg1, t0, t1, lat_ref, lon_ref):
|
||||||
@@ -32,8 +12,8 @@ def surface_position(msg0, msg1, t0, t1, lat_ref, lon_ref):
|
|||||||
the lat/lon of receiver must be provided to yield the correct solution.
|
the lat/lon of receiver must be provided to yield the correct solution.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg0 (string): even message (28 bytes hexadecimal string)
|
msg0 (string): even message (28 hexdigits)
|
||||||
msg1 (string): odd message (28 bytes hexadecimal string)
|
msg1 (string): odd message (28 hexdigits)
|
||||||
t0 (int): timestamps for the even message
|
t0 (int): timestamps for the even message
|
||||||
t1 (int): timestamps for the odd message
|
t1 (int): timestamps for the odd message
|
||||||
lat_ref (float): latitude of the receiver
|
lat_ref (float): latitude of the receiver
|
||||||
@@ -75,22 +55,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__)
|
||||||
@@ -106,7 +89,7 @@ def surface_position_with_ref(msg, lat_ref, lon_ref):
|
|||||||
be with in 45NM of the true position.
|
be with in 45NM of the true position.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (string): even message (28 bytes hexadecimal string)
|
msg (str): even message (28 hexdigits)
|
||||||
lat_ref: previous known latitude
|
lat_ref: previous known latitude
|
||||||
lon_ref: previous known longitude
|
lon_ref: previous known longitude
|
||||||
|
|
||||||
@@ -114,17 +97,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)
|
||||||
|
|
||||||
@@ -135,25 +118,33 @@ 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)
|
||||||
|
|
||||||
return round(lat, 5), round(lon, 5)
|
return round(lat, 5), round(lon, 5)
|
||||||
|
|
||||||
|
|
||||||
def surface_velocity(msg):
|
def surface_velocity(msg, source=False):
|
||||||
"""Decode surface velocity from from a surface position message
|
"""Decode surface velocity from a surface position message
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (string): 28 bytes hexadecimal message string
|
msg (str): 28 hexdigits string
|
||||||
|
source (boolean): Include direction and vertical rate sources in return. Default to False.
|
||||||
|
If set to True, the function will return six value instead of four.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
(int, float, int, string): speed (kt), ground track (degree),
|
int, float, int, string, [string], [string]: Four or six parameters, including:
|
||||||
rate of climb/descend (ft/min), and speed type
|
- Speed (kt)
|
||||||
('GS' for ground speed, 'AS' for airspeed)
|
- Angle (degree), ground track
|
||||||
"""
|
- Vertical rate, always 0
|
||||||
|
- Speed type ('GS' for ground speed, 'AS' for airspeed)
|
||||||
|
- [Optional] Direction source ('TRUE_NORTH')
|
||||||
|
- [Optional] Vertical rate source (None)
|
||||||
|
|
||||||
|
"""
|
||||||
if common.typecode(msg) < 5 or common.typecode(msg) > 8:
|
if common.typecode(msg) < 5 or common.typecode(msg) > 8:
|
||||||
raise RuntimeError("%s: Not a surface message, expecting 5<TC<8" % msg)
|
raise RuntimeError("%s: Not a surface message, expecting 5<TC<8" % msg)
|
||||||
|
|
||||||
@@ -167,7 +158,7 @@ def surface_velocity(msg):
|
|||||||
else:
|
else:
|
||||||
trk = None
|
trk = None
|
||||||
|
|
||||||
# ground movment / speed
|
# ground movement / speed
|
||||||
mov = common.bin2int(mb[5:12])
|
mov = common.bin2int(mb[5:12])
|
||||||
|
|
||||||
if mov == 0 or mov > 124:
|
if mov == 0 or mov > 124:
|
||||||
@@ -180,8 +171,11 @@ def surface_velocity(msg):
|
|||||||
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)
|
||||||
|
|
||||||
return spd, trk, 0, 'GS'
|
if source:
|
||||||
|
return spd, trk, 0, "GS", "TRUE_NORTH", None
|
||||||
|
else:
|
||||||
|
return spd, trk, 0, "GS"
|
||||||
|
|||||||
@@ -1,35 +1,17 @@
|
|||||||
# Copyright (C) 2018 Junzi Sun (TU Delft)
|
# ------------------------------------------
|
||||||
|
# BDS 0,8
|
||||||
|
# ADS-B TC=1-4
|
||||||
|
# Aircraft identitification and category
|
||||||
|
# ------------------------------------------
|
||||||
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
from pyModeS import common
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
------------------------------------------
|
|
||||||
BDS 0,8
|
|
||||||
ADS-B TC=1-4
|
|
||||||
Aircraft identitification and category
|
|
||||||
------------------------------------------
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, print_function, division
|
|
||||||
from pyModeS.decoder import common
|
|
||||||
|
|
||||||
def category(msg):
|
def category(msg):
|
||||||
"""Aircraft category number
|
"""Aircraft category number
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (string): 28 bytes hexadecimal message string
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
int: category number
|
int: category number
|
||||||
@@ -39,14 +21,15 @@ 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):
|
||||||
"""Aircraft callsign
|
"""Aircraft callsign
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (string): 28 bytes hexadecimal message string
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
string: callsign
|
string: callsign
|
||||||
@@ -55,11 +38,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])]
|
||||||
@@ -71,5 +54,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,44 +1,33 @@
|
|||||||
# Copyright (C) 2018 Junzi Sun (TU Delft)
|
# ------------------------------------------
|
||||||
|
# BDS 0,9
|
||||||
|
# ADS-B TC=19
|
||||||
|
# Aircraft Airborn velocity
|
||||||
|
# ------------------------------------------
|
||||||
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
from pyModeS import common
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
------------------------------------------
|
|
||||||
BDS 0,9
|
|
||||||
ADS-B TC=19
|
|
||||||
Aircraft Airborn velocity
|
|
||||||
------------------------------------------
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, print_function, division
|
|
||||||
from pyModeS.decoder import common
|
|
||||||
import math
|
import math
|
||||||
|
|
||||||
|
|
||||||
def airborne_velocity(msg):
|
def airborne_velocity(msg, source=False):
|
||||||
"""Calculate the speed, track (or heading), and vertical rate
|
"""Decode airborne velocity.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (string): 28 bytes hexadecimal message string
|
msg (str): 28 hexdigits string
|
||||||
|
source (boolean): Include direction and vertical rate sources in return. Default to False.
|
||||||
|
If set to True, the function will return six value instead of four.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
(int, float, int, string): speed (kt), ground track or heading (degree),
|
int, float, int, string, [string], [string]: Four or six parameters, including:
|
||||||
rate of climb/descend (ft/min), and speed type
|
- Speed (kt)
|
||||||
('GS' for ground speed, 'AS' for airspeed)
|
- Angle (degree), either ground track or heading
|
||||||
"""
|
- Vertical rate (ft/min)
|
||||||
|
- Speed type ('GS' for ground speed, 'AS' for airspeed)
|
||||||
|
- [Optional] Direction source ('TRUE_NORTH' or 'MAGENTIC_NORTH')
|
||||||
|
- [Optional] Vertical rate source ('BARO' or 'GNSS')
|
||||||
|
|
||||||
|
"""
|
||||||
if common.typecode(msg) != 19:
|
if common.typecode(msg) != 19:
|
||||||
raise RuntimeError("%s: Not a airborne velocity message, expecting TC=19" % msg)
|
raise RuntimeError("%s: Not a airborne velocity message, expecting TC=19" % msg)
|
||||||
|
|
||||||
@@ -50,27 +39,32 @@ def airborne_velocity(msg):
|
|||||||
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
|
||||||
|
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
|
||||||
|
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'
|
spd_type = "GS"
|
||||||
trk_or_hdg = round(trk, 2)
|
trk_or_hdg = round(trk, 2)
|
||||||
|
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
|
||||||
@@ -79,28 +73,38 @@ def airborne_velocity(msg):
|
|||||||
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
|
||||||
|
spd *= 4
|
||||||
|
|
||||||
if mb[24]=='0':
|
if mb[24] == "0":
|
||||||
tag = 'IAS'
|
spd_type = "IAS"
|
||||||
else:
|
else:
|
||||||
tag = 'TAS'
|
spd_type = "TAS"
|
||||||
|
|
||||||
vr_sign = -1 if mb[36]=='1' else 1
|
dir_type = "MAGENTIC_NORTH"
|
||||||
|
|
||||||
|
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)
|
vs = None if vr == 0 else int(vr_sign * (vr - 1) * 64)
|
||||||
|
|
||||||
|
if source:
|
||||||
|
return spd, trk_or_hdg, vs, spd_type, dir_type, vr_source
|
||||||
|
else:
|
||||||
|
return spd, trk_or_hdg, vs, spd_type
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (string): 28 bytes hexadecimal message string, TC=19
|
msg (str): 28 hexdigits string, TC=19
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
int: Altitude difference in ft. Negative value indicates GNSS altitude
|
int: Altitude difference in feet. Negative value indicates GNSS altitude
|
||||||
below barometric altitude.
|
below barometric altitude.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
tc = common.typecode(msg)
|
tc = common.typecode(msg)
|
||||||
|
|
||||||
@@ -114,4 +118,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,66 +1,52 @@
|
|||||||
# 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/>.
|
|
||||||
|
|
||||||
from __future__ import absolute_import, print_function, division
|
|
||||||
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros
|
|
||||||
|
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
# BDS 1,0
|
# BDS 1,0
|
||||||
# Data link capability report
|
# Data link capability report
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
|
|
||||||
|
from pyModeS import common
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (String): 28 bytes hexadecimal message string
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: True or False
|
bool: True or False
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if allzeros(msg):
|
if common.allzeros(msg):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
d = hex2bin(data(msg))
|
d = common.hex2bin(common.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
|
||||||
if bin2int(d[9:14]) != 0:
|
if common.bin2int(d[9:14]) != 0:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# overlay capabilty conflict
|
# overlay capability conflict
|
||||||
if d[14] == '1' and bin2int(d[16:23]) < 5:
|
if d[14] == "1" and common.bin2int(d[16:23]) < 5:
|
||||||
return False
|
return False
|
||||||
if d[14] == '0' and bin2int(d[16:23]) > 4:
|
if d[14] == "0" and common.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
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (String): 28 bytes hexadecimal message string
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
int: Whether the transponder is OVC capable
|
int: Whether the transponder is OVC capable
|
||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = common.hex2bin(common.data(msg))
|
||||||
|
|
||||||
return int(d[14])
|
return int(d[14])
|
||||||
|
|||||||
@@ -1,45 +1,27 @@
|
|||||||
# Copyright (C) 2018 Junzi Sun (TU Delft)
|
# ------------------------------------------
|
||||||
|
# BDS 1,7
|
||||||
|
# Common usage GICB capability report
|
||||||
|
# ------------------------------------------
|
||||||
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
from pyModeS import common
|
||||||
# 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/>.
|
|
||||||
|
|
||||||
|
|
||||||
from __future__ import absolute_import, print_function, division
|
|
||||||
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros
|
|
||||||
|
|
||||||
"""
|
|
||||||
------------------------------------------
|
|
||||||
BDS 1,7
|
|
||||||
Common usage GICB capability report
|
|
||||||
------------------------------------------
|
|
||||||
"""
|
|
||||||
|
|
||||||
def is17(msg):
|
def is17(msg):
|
||||||
"""Check if a message is likely to be BDS code 1,7
|
"""Check if a message is likely to be BDS code 1,7
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (String): 28 bytes hexadecimal message string
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: True or False
|
bool: True or False
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if allzeros(msg):
|
if common.allzeros(msg):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
d = hex2bin(data(msg))
|
d = common.hex2bin(common.data(msg))
|
||||||
|
|
||||||
if bin2int(d[28:56]) != 0:
|
if common.bin2int(d[28:56]) != 0:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
caps = cap17(msg)
|
caps = cap17(msg)
|
||||||
@@ -50,26 +32,54 @@ 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
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (String): 28 bytes hexadecimal message string
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
list: list of suport BDS codes
|
list: list of support 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 = common.hex2bin(common.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,47 +1,32 @@
|
|||||||
# 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/>.
|
|
||||||
|
|
||||||
from __future__ import absolute_import, print_function, division
|
|
||||||
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros
|
|
||||||
|
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
# BDS 2,0
|
# BDS 2,0
|
||||||
# Aircraft identification
|
# Aircraft identification
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
|
|
||||||
|
from pyModeS import common
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (String): 28 bytes hexadecimal message string
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: True or False
|
bool: True or False
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if allzeros(msg):
|
if common.allzeros(msg):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
d = hex2bin(data(msg))
|
d = common.hex2bin(common.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
|
||||||
@@ -51,23 +36,23 @@ def cs20(msg):
|
|||||||
"""Aircraft callsign
|
"""Aircraft callsign
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (String): 28 bytes hexadecimal message (BDS40) string
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
string: callsign, max. 8 chars
|
string: callsign, max. 8 chars
|
||||||
"""
|
"""
|
||||||
chars = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ#####_###############0123456789######'
|
chars = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ#####_###############0123456789######"
|
||||||
|
|
||||||
d = hex2bin(data(msg))
|
d = common.hex2bin(common.data(msg))
|
||||||
|
|
||||||
cs = ''
|
cs = ""
|
||||||
cs += chars[bin2int(d[8:14])]
|
cs += chars[common.bin2int(d[8:14])]
|
||||||
cs += chars[bin2int(d[14:20])]
|
cs += chars[common.bin2int(d[14:20])]
|
||||||
cs += chars[bin2int(d[20:26])]
|
cs += chars[common.bin2int(d[20:26])]
|
||||||
cs += chars[bin2int(d[26:32])]
|
cs += chars[common.bin2int(d[26:32])]
|
||||||
cs += chars[bin2int(d[32:38])]
|
cs += chars[common.bin2int(d[32:38])]
|
||||||
cs += chars[bin2int(d[38:44])]
|
cs += chars[common.bin2int(d[38:44])]
|
||||||
cs += chars[bin2int(d[44:50])]
|
cs += chars[common.bin2int(d[44:50])]
|
||||||
cs += chars[bin2int(d[50:56])]
|
cs += chars[common.bin2int(d[50:56])]
|
||||||
|
|
||||||
return cs
|
return cs
|
||||||
|
|||||||
@@ -1,50 +1,35 @@
|
|||||||
# 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/>.
|
|
||||||
|
|
||||||
from __future__ import absolute_import, print_function, division
|
|
||||||
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros
|
|
||||||
|
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
# BDS 3,0
|
# BDS 3,0
|
||||||
# ACAS active resolution advisory
|
# ACAS active resolution advisory
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
|
|
||||||
|
from pyModeS import common
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (String): 28 bytes hexadecimal message string
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: True or False
|
bool: True or False
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if allzeros(msg):
|
if common.allzeros(msg):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
d = hex2bin(data(msg))
|
d = common.hex2bin(common.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
|
||||||
if bin2int(d[15:22]) >= 48:
|
if common.bin2int(d[15:22]) >= 48:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -1,103 +1,88 @@
|
|||||||
# 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/>.
|
|
||||||
|
|
||||||
|
|
||||||
from __future__ import absolute_import, print_function, division
|
|
||||||
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros, wrongstatus
|
|
||||||
|
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
# BDS 4,0
|
# BDS 4,0
|
||||||
# Selected vertical intention
|
# Selected vertical intention
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
|
|
||||||
|
import warnings
|
||||||
|
from pyModeS import common
|
||||||
|
|
||||||
|
|
||||||
def is40(msg):
|
def is40(msg):
|
||||||
"""Check if a message is likely to be BDS code 4,0
|
"""Check if a message is likely to be BDS code 4,0
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (String): 28 bytes hexadecimal message string
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: True or False
|
bool: True or False
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if allzeros(msg):
|
if common.allzeros(msg):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
d = hex2bin(data(msg))
|
d = common.hex2bin(common.data(msg))
|
||||||
|
|
||||||
# status bit 1, 14, and 27
|
# status bit 1, 14, and 27
|
||||||
|
|
||||||
if wrongstatus(d, 1, 2, 13):
|
if common.wrongstatus(d, 1, 2, 13):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if wrongstatus(d, 14, 15, 26):
|
if common.wrongstatus(d, 14, 15, 26):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if wrongstatus(d, 27, 28, 39):
|
if common.wrongstatus(d, 27, 28, 39):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if wrongstatus(d, 48, 49, 51):
|
if common.wrongstatus(d, 48, 49, 51):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if wrongstatus(d, 54, 55, 56):
|
if common.wrongstatus(d, 54, 55, 56):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# bits 40-47 and 52-53 shall all be zero
|
# bits 40-47 and 52-53 shall all be zero
|
||||||
|
|
||||||
if bin2int(d[39:47]) != 0:
|
if common.bin2int(d[39:47]) != 0:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if bin2int(d[51:53]) != 0:
|
if common.bin2int(d[51:53]) != 0:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def alt40mcp(msg):
|
def selalt40mcp(msg):
|
||||||
"""Selected altitude, MCP/FCU
|
"""Selected altitude, MCP/FCU
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (String): 28 bytes hexadecimal message (BDS40) string
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
int: altitude in feet
|
int: altitude in feet
|
||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = common.hex2bin(common.data(msg))
|
||||||
|
|
||||||
if d[0] == '0':
|
if d[0] == "0":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
alt = bin2int(d[1:13]) * 16 # ft
|
alt = common.bin2int(d[1:13]) * 16 # ft
|
||||||
return alt
|
return alt
|
||||||
|
|
||||||
|
|
||||||
def alt40fms(msg):
|
def selalt40fms(msg):
|
||||||
"""Selected altitude, FMS
|
"""Selected altitude, FMS
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (String): 28 bytes hexadecimal message (BDS40) string
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
int: altitude in feet
|
int: altitude in feet
|
||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = common.hex2bin(common.data(msg))
|
||||||
|
|
||||||
if d[13] == '0':
|
if d[13] == "0":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
alt = bin2int(d[14:26]) * 16 # ft
|
alt = common.bin2int(d[14:26]) * 16 # ft
|
||||||
return alt
|
return alt
|
||||||
|
|
||||||
|
|
||||||
@@ -105,15 +90,31 @@ def p40baro(msg):
|
|||||||
"""Barometric pressure setting
|
"""Barometric pressure setting
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (String): 28 bytes hexadecimal message (BDS40) string
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
float: pressure in millibar
|
float: pressure in millibar
|
||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = common.hex2bin(common.data(msg))
|
||||||
|
|
||||||
if d[26] == '0':
|
if d[26] == "0":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
p = bin2int(d[27:39]) * 0.1 + 800 # millibar
|
p = common.bin2int(d[27:39]) * 0.1 + 800 # millibar
|
||||||
return p
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
def alt40mcp(msg):
|
||||||
|
warnings.warn(
|
||||||
|
"alt40mcp() has been renamed to selalt40mcp(). It will be removed in the future.",
|
||||||
|
DeprecationWarning,
|
||||||
|
)
|
||||||
|
return selalt40mcp(msg)
|
||||||
|
|
||||||
|
|
||||||
|
def alt40fms(msg):
|
||||||
|
warnings.warn(
|
||||||
|
"alt40fms() has been renamed to selalt40fms(). It will be removed in the future.",
|
||||||
|
DeprecationWarning,
|
||||||
|
)
|
||||||
|
return selalt40mcp(msg)
|
||||||
|
|||||||
@@ -1,218 +1,161 @@
|
|||||||
# 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/>.
|
|
||||||
|
|
||||||
from __future__ import absolute_import, print_function, division
|
|
||||||
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros, wrongstatus
|
|
||||||
|
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
# BDS 4,4
|
# BDS 4,4
|
||||||
# Meteorological routine air report
|
# Meteorological routine air report
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
|
|
||||||
def is44(msg, rev=False):
|
from pyModeS import common
|
||||||
"""Check if a message is likely to be BDS code 4,4
|
|
||||||
|
|
||||||
|
def is44(msg):
|
||||||
|
"""Check if a message is likely to be BDS code 4,4.
|
||||||
|
|
||||||
Meteorological routine air report
|
Meteorological routine air report
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (String): 28 bytes hexadecimal message string
|
msg (str): 28 hexdigits string
|
||||||
rev (bool): using revised version
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: True or False
|
bool: True or False
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
if common.allzeros(msg):
|
||||||
if allzeros(msg):
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
d = hex2bin(data(msg))
|
d = common.hex2bin(common.data(msg))
|
||||||
|
|
||||||
|
# status bit 5, 35, 47, 50
|
||||||
if not rev:
|
if common.wrongstatus(d, 5, 6, 23):
|
||||||
# status bit 5, 35, 47, 50
|
|
||||||
if wrongstatus(d, 5, 6, 23):
|
|
||||||
return False
|
|
||||||
|
|
||||||
if wrongstatus(d, 35, 36, 46):
|
|
||||||
return False
|
|
||||||
|
|
||||||
if wrongstatus(d, 47, 48, 49):
|
|
||||||
return False
|
|
||||||
|
|
||||||
if wrongstatus(d, 50, 51, 56):
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Bits 1-4 indicate source, values > 4 reserved and should not occur
|
|
||||||
if bin2int(d[0:4]) > 4:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
# status bit 5, 15, 24, 36, 49
|
|
||||||
if wrongstatus(d, 5, 6, 14):
|
|
||||||
return False
|
|
||||||
|
|
||||||
if wrongstatus(d, 15, 16, 23):
|
|
||||||
return False
|
|
||||||
|
|
||||||
if wrongstatus(d, 24, 25, 35):
|
|
||||||
return False
|
|
||||||
|
|
||||||
if wrongstatus(d, 36, 37, 47):
|
|
||||||
return False
|
|
||||||
|
|
||||||
if wrongstatus(d, 49, 50, 56):
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Bits 1-4 are reserved and should be zero
|
|
||||||
if bin2int(d[0:4]) != 0:
|
|
||||||
return False
|
|
||||||
|
|
||||||
vw = wind44(msg, rev=rev)
|
|
||||||
if vw is not None and vw[0] > 250:
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if temp44(msg):
|
if common.wrongstatus(d, 35, 36, 46):
|
||||||
if temp44(msg) > 60 or temp44(msg) < -80:
|
return False
|
||||||
return False
|
|
||||||
|
|
||||||
elif temp44(msg) == 0:
|
if common.wrongstatus(d, 47, 48, 49):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if common.wrongstatus(d, 50, 51, 56):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Bits 1-4 indicate source, values > 4 reserved and should not occur
|
||||||
|
if common.bin2int(d[0:4]) > 4:
|
||||||
|
return False
|
||||||
|
|
||||||
|
vw, dw = wind44(msg)
|
||||||
|
if vw is not None and vw > 250:
|
||||||
|
return False
|
||||||
|
|
||||||
|
temp, temp2 = temp44(msg)
|
||||||
|
if min(temp, temp2) > 60 or max(temp, temp2) < -80:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def wind44(msg, rev=False):
|
def wind44(msg):
|
||||||
"""reported wind speed and direction
|
"""Wind speed and direction.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (String): 28 bytes hexadecimal message (BDS44) string
|
msg (str): 28 hexdigits string
|
||||||
rev (bool): using revised version
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
(int, float): speed (kt), direction (degree)
|
(int, float): speed (kt), direction (degree)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = common.hex2bin(common.data(msg))
|
||||||
|
|
||||||
if not rev:
|
status = int(d[4])
|
||||||
status = int(d[4])
|
if not status:
|
||||||
if not status:
|
return None, None
|
||||||
return None
|
|
||||||
|
|
||||||
speed = bin2int(d[5:14]) # knots
|
speed = common.bin2int(d[5:14]) # knots
|
||||||
direction = bin2int(d[14:23]) * 180.0 / 256.0 # degree
|
direction = common.bin2int(d[14:23]) * 180.0 / 256.0 # degree
|
||||||
|
|
||||||
else:
|
|
||||||
spd_status = int(d[4])
|
|
||||||
dir_status = int(d[14])
|
|
||||||
|
|
||||||
if (not spd_status) or (not dir_status):
|
|
||||||
return None
|
|
||||||
|
|
||||||
speed = bin2int(d[5:14]) # knots
|
|
||||||
direction = bin2int(d[15:23]) * 180.0 / 128.0 # degree
|
|
||||||
|
|
||||||
return round(speed, 0), round(direction, 1)
|
return round(speed, 0), round(direction, 1)
|
||||||
|
|
||||||
|
|
||||||
def temp44(msg, rev=False):
|
def temp44(msg):
|
||||||
"""reported air temperature
|
"""Static air temperature.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (String): 28 bytes hexadecimal message (BDS44) string
|
msg (str): 28 hexdigits string
|
||||||
rev (bool): using revised version
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
float: tmeperature in Celsius degree
|
float, float: temperature and alternative temperature in Celsius degree.
|
||||||
|
Note: Two values returns due to what seems to be an inconsistency
|
||||||
|
error in ICAO 9871 (2008) Appendix A-67.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = common.hex2bin(common.data(msg))
|
||||||
|
|
||||||
if not rev:
|
sign = int(d[23])
|
||||||
# if d[22] == '0':
|
value = common.bin2int(d[24:34])
|
||||||
# return None
|
|
||||||
|
|
||||||
sign = int(d[23])
|
if sign:
|
||||||
value = bin2int(d[24:34])
|
value = value - 1024
|
||||||
|
|
||||||
if sign:
|
temp = value * 0.25 # celsius
|
||||||
value = value - 1024
|
temp = round(temp, 2)
|
||||||
|
|
||||||
temp = value * 0.125 # celsius
|
temp_alternative = value * 0.125 # celsius
|
||||||
temp = round(temp, 1)
|
temp_alternative = round(temp_alternative, 3)
|
||||||
else:
|
|
||||||
# if d[23] == '0':
|
|
||||||
# return None
|
|
||||||
|
|
||||||
sign = int(d[24])
|
return temp, temp_alternative
|
||||||
value = bin2int(d[25:35])
|
|
||||||
|
|
||||||
if sign:
|
|
||||||
value = value - 1024
|
|
||||||
|
|
||||||
temp = value * 0.125 # celsius
|
|
||||||
temp = round(temp, 1)
|
|
||||||
|
|
||||||
return temp
|
|
||||||
|
|
||||||
|
|
||||||
def p44(msg, rev=False):
|
def p44(msg):
|
||||||
"""reported average static pressure
|
"""Static pressure.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (String): 28 bytes hexadecimal message (BDS44) string
|
msg (str): 28 hexdigits string
|
||||||
rev (bool): using revised version
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
int: static pressure in hPa
|
int: static pressure in hPa
|
||||||
|
|
||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = common.hex2bin(common.data(msg))
|
||||||
|
|
||||||
if not rev:
|
if d[34] == "0":
|
||||||
if d[34] == '0':
|
return None
|
||||||
return None
|
|
||||||
|
|
||||||
p = bin2int(d[35:46]) # hPa
|
p = common.bin2int(d[35:46]) # hPa
|
||||||
|
|
||||||
else:
|
|
||||||
if d[35] == '0':
|
|
||||||
return None
|
|
||||||
|
|
||||||
p = bin2int(d[36:47]) # hPa
|
|
||||||
|
|
||||||
return p
|
return p
|
||||||
|
|
||||||
|
|
||||||
def hum44(msg, rev=False):
|
def hum44(msg):
|
||||||
"""reported humidity
|
"""humidity
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (String): 28 bytes hexadecimal message (BDS44) string
|
msg (str): 28 hexdigits string
|
||||||
rev (bool): using revised version
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
float: percentage of humidity, [0 - 100] %
|
float: percentage of humidity, [0 - 100] %
|
||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = common.hex2bin(common.data(msg))
|
||||||
|
|
||||||
if not rev:
|
if d[49] == "0":
|
||||||
if d[49] == '0':
|
return None
|
||||||
return None
|
|
||||||
|
|
||||||
hm = bin2int(d[50:56]) * 100.0 / 64 # %
|
hm = common.bin2int(d[50:56]) * 100.0 / 64 # %
|
||||||
|
|
||||||
else:
|
|
||||||
if d[48] == '0':
|
|
||||||
return None
|
|
||||||
|
|
||||||
hm = bin2int(d[49:56]) # %
|
|
||||||
|
|
||||||
return round(hm, 1)
|
return round(hm, 1)
|
||||||
|
|
||||||
|
|
||||||
|
def turb44(msg):
|
||||||
|
"""Turblence.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: turbulence level. 0=NIL, 1=Light, 2=Moderate, 3=Severe
|
||||||
|
|
||||||
|
"""
|
||||||
|
d = common.hex2bin(common.data(msg))
|
||||||
|
|
||||||
|
if d[46] == "0":
|
||||||
|
return None
|
||||||
|
|
||||||
|
turb = common.bin2int(d[47:49])
|
||||||
|
|
||||||
|
return turb
|
||||||
|
|||||||
208
pyModeS/decoder/bds/bds45.py
Normal file
208
pyModeS/decoder/bds/bds45.py
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
# ------------------------------------------
|
||||||
|
# BDS 4,5
|
||||||
|
# Meteorological hazard report
|
||||||
|
# ------------------------------------------
|
||||||
|
|
||||||
|
from pyModeS import common
|
||||||
|
|
||||||
|
|
||||||
|
def is45(msg):
|
||||||
|
"""Check if a message is likely to be BDS code 4,5.
|
||||||
|
|
||||||
|
Meteorological hazard report
|
||||||
|
|
||||||
|
Args:
|
||||||
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True or False
|
||||||
|
|
||||||
|
"""
|
||||||
|
if common.allzeros(msg):
|
||||||
|
return False
|
||||||
|
|
||||||
|
d = common.hex2bin(common.data(msg))
|
||||||
|
|
||||||
|
# status bit 1, 4, 7, 10, 13, 16, 27, 39
|
||||||
|
if common.wrongstatus(d, 1, 2, 3):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if common.wrongstatus(d, 4, 5, 6):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if common.wrongstatus(d, 7, 8, 9):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if common.wrongstatus(d, 10, 11, 12):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if common.wrongstatus(d, 13, 14, 15):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if common.wrongstatus(d, 16, 17, 26):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if common.wrongstatus(d, 27, 28, 38):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if common.wrongstatus(d, 39, 40, 51):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# reserved
|
||||||
|
if common.bin2int(d[51:56]) != 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
temp = temp45(msg)
|
||||||
|
if temp:
|
||||||
|
if temp > 60 or temp < -80:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def turb45(msg):
|
||||||
|
"""Turbulence.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: Turbulence level. 0=NIL, 1=Light, 2=Moderate, 3=Severe
|
||||||
|
|
||||||
|
"""
|
||||||
|
d = common.hex2bin(common.data(msg))
|
||||||
|
if d[0] == "0":
|
||||||
|
return None
|
||||||
|
|
||||||
|
turb = common.bin2int(d[1:3])
|
||||||
|
return turb
|
||||||
|
|
||||||
|
|
||||||
|
def ws45(msg):
|
||||||
|
"""Wind shear.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: Wind shear level. 0=NIL, 1=Light, 2=Moderate, 3=Severe
|
||||||
|
|
||||||
|
"""
|
||||||
|
d = common.hex2bin(common.data(msg))
|
||||||
|
if d[3] == "0":
|
||||||
|
return None
|
||||||
|
|
||||||
|
ws = common.bin2int(d[4:6])
|
||||||
|
return ws
|
||||||
|
|
||||||
|
|
||||||
|
def mb45(msg):
|
||||||
|
"""Microburst.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: Microburst level. 0=NIL, 1=Light, 2=Moderate, 3=Severe
|
||||||
|
|
||||||
|
"""
|
||||||
|
d = common.hex2bin(common.data(msg))
|
||||||
|
if d[6] == "0":
|
||||||
|
return None
|
||||||
|
|
||||||
|
mb = common.bin2int(d[7:9])
|
||||||
|
return mb
|
||||||
|
|
||||||
|
|
||||||
|
def ic45(msg):
|
||||||
|
"""Icing.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: Icing level. 0=NIL, 1=Light, 2=Moderate, 3=Severe
|
||||||
|
|
||||||
|
"""
|
||||||
|
d = common.hex2bin(common.data(msg))
|
||||||
|
if d[9] == "0":
|
||||||
|
return None
|
||||||
|
|
||||||
|
ic = common.bin2int(d[10:12])
|
||||||
|
return ic
|
||||||
|
|
||||||
|
|
||||||
|
def wv45(msg):
|
||||||
|
"""Wake vortex.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: Wake vortex level. 0=NIL, 1=Light, 2=Moderate, 3=Severe
|
||||||
|
|
||||||
|
"""
|
||||||
|
d = common.hex2bin(common.data(msg))
|
||||||
|
if d[12] == "0":
|
||||||
|
return None
|
||||||
|
|
||||||
|
ws = common.bin2int(d[13:15])
|
||||||
|
return ws
|
||||||
|
|
||||||
|
|
||||||
|
def temp45(msg):
|
||||||
|
"""Static air temperature.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
float: tmeperature in Celsius degree
|
||||||
|
|
||||||
|
"""
|
||||||
|
d = common.hex2bin(common.data(msg))
|
||||||
|
|
||||||
|
sign = int(d[16])
|
||||||
|
value = common.bin2int(d[17:26])
|
||||||
|
|
||||||
|
if sign:
|
||||||
|
value = value - 512
|
||||||
|
|
||||||
|
temp = value * 0.25 # celsius
|
||||||
|
temp = round(temp, 1)
|
||||||
|
|
||||||
|
return temp
|
||||||
|
|
||||||
|
|
||||||
|
def p45(msg):
|
||||||
|
"""Average static pressure.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: static pressure in hPa
|
||||||
|
|
||||||
|
"""
|
||||||
|
d = common.hex2bin(common.data(msg))
|
||||||
|
if d[26] == "0":
|
||||||
|
return None
|
||||||
|
p = common.bin2int(d[27:38]) # hPa
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
def rh45(msg):
|
||||||
|
"""Radio height.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: radio height in ft
|
||||||
|
|
||||||
|
"""
|
||||||
|
d = common.hex2bin(common.data(msg))
|
||||||
|
if d[38] == "0":
|
||||||
|
return None
|
||||||
|
rh = common.bin2int(d[39:51]) * 16
|
||||||
|
return rh
|
||||||
@@ -1,57 +1,42 @@
|
|||||||
# 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/>.
|
|
||||||
|
|
||||||
from __future__ import absolute_import, print_function, division
|
|
||||||
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros, wrongstatus
|
|
||||||
|
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
# BDS 5,0
|
# BDS 5,0
|
||||||
# Track and turn report
|
# Track and turn report
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
|
|
||||||
|
from pyModeS import common
|
||||||
|
|
||||||
|
|
||||||
def is50(msg):
|
def is50(msg):
|
||||||
"""Check if a message is likely to be BDS code 5,0
|
"""Check if a message is likely to be BDS code 5,0
|
||||||
(Track and turn report)
|
(Track and turn report)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (String): 28 bytes hexadecimal message string
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: True or False
|
bool: True or False
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if allzeros(msg):
|
if common.allzeros(msg):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
d = hex2bin(data(msg))
|
d = common.hex2bin(common.data(msg))
|
||||||
|
|
||||||
# status bit 1, 12, 24, 35, 46
|
# status bit 1, 12, 24, 35, 46
|
||||||
|
|
||||||
if wrongstatus(d, 1, 3, 11):
|
if common.wrongstatus(d, 1, 3, 11):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if wrongstatus(d, 12, 13, 23):
|
if common.wrongstatus(d, 12, 13, 23):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if wrongstatus(d, 24, 25, 34):
|
if common.wrongstatus(d, 24, 25, 34):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if wrongstatus(d, 35, 36, 45):
|
if common.wrongstatus(d, 35, 36, 45):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if wrongstatus(d, 46, 47, 56):
|
if common.wrongstatus(d, 46, 47, 56):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
roll = roll50(msg)
|
roll = roll50(msg)
|
||||||
@@ -76,24 +61,24 @@ def roll50(msg):
|
|||||||
"""Roll angle, BDS 5,0 message
|
"""Roll angle, BDS 5,0 message
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (String): 28 bytes hexadecimal message (BDS50) string
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
float: angle in degrees,
|
float: angle in degrees,
|
||||||
negative->left wing down, positive->right wing down
|
negative->left wing down, positive->right wing down
|
||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = common.hex2bin(common.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 = common.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)
|
||||||
|
|
||||||
|
|
||||||
@@ -101,18 +86,18 @@ def trk50(msg):
|
|||||||
"""True track angle, BDS 5,0 message
|
"""True track angle, BDS 5,0 message
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (String): 28 bytes hexadecimal message (BDS50) string
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
float: angle in degrees to true north (from 0 to 360)
|
float: angle in degrees to true north (from 0 to 360)
|
||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = common.hex2bin(common.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 = common.bin2int(d[13:23])
|
||||||
|
|
||||||
if sign:
|
if sign:
|
||||||
value = value - 1024
|
value = value - 1024
|
||||||
@@ -130,17 +115,17 @@ def gs50(msg):
|
|||||||
"""Ground speed, BDS 5,0 message
|
"""Ground speed, BDS 5,0 message
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (String): 28 bytes hexadecimal message (BDS50) string
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
int: ground speed in knots
|
int: ground speed in knots
|
||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = common.hex2bin(common.data(msg))
|
||||||
|
|
||||||
if d[23] == '0':
|
if d[23] == "0":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
spd = bin2int(d[24:34]) * 2 # kts
|
spd = common.bin2int(d[24:34]) * 2 # kts
|
||||||
return spd
|
return spd
|
||||||
|
|
||||||
|
|
||||||
@@ -148,25 +133,25 @@ def rtrk50(msg):
|
|||||||
"""Track angle rate, BDS 5,0 message
|
"""Track angle rate, BDS 5,0 message
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (String): 28 bytes hexadecimal message (BDS50) string
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
float: angle rate in degrees/second
|
float: angle rate in degrees/second
|
||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = common.hex2bin(common.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 = common.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)
|
||||||
|
|
||||||
|
|
||||||
@@ -174,15 +159,15 @@ def tas50(msg):
|
|||||||
"""Aircraft true airspeed, BDS 5,0 message
|
"""Aircraft true airspeed, BDS 5,0 message
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (String): 28 bytes hexadecimal message (BDS50) string
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
int: true airspeed in knots
|
int: true airspeed in knots
|
||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = common.hex2bin(common.data(msg))
|
||||||
|
|
||||||
if d[45] == '0':
|
if d[45] == "0":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
tas = bin2int(d[46:56]) * 2 # kts
|
tas = common.bin2int(d[46:56]) * 2 # kts
|
||||||
return tas
|
return tas
|
||||||
|
|||||||
@@ -1,57 +1,42 @@
|
|||||||
# 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/>.
|
|
||||||
|
|
||||||
from __future__ import absolute_import, print_function, division
|
|
||||||
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros, wrongstatus
|
|
||||||
|
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
# BDS 5,3
|
# BDS 5,3
|
||||||
# Air-referenced state vector
|
# Air-referenced state vector
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
|
|
||||||
|
from pyModeS import common
|
||||||
|
|
||||||
|
|
||||||
def is53(msg):
|
def is53(msg):
|
||||||
"""Check if a message is likely to be BDS code 5,3
|
"""Check if a message is likely to be BDS code 5,3
|
||||||
(Air-referenced state vector)
|
(Air-referenced state vector)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (String): 28 bytes hexadecimal message string
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: True or False
|
bool: True or False
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if allzeros(msg):
|
if common.allzeros(msg):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
d = hex2bin(data(msg))
|
d = common.hex2bin(common.data(msg))
|
||||||
|
|
||||||
# status bit 1, 13, 24, 34, 47
|
# status bit 1, 13, 24, 34, 47
|
||||||
|
|
||||||
if wrongstatus(d, 1, 3, 12):
|
if common.wrongstatus(d, 1, 3, 12):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if wrongstatus(d, 13, 14, 23):
|
if common.wrongstatus(d, 13, 14, 23):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if wrongstatus(d, 24, 25, 33):
|
if common.wrongstatus(d, 24, 25, 33):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if wrongstatus(d, 34, 35, 46):
|
if common.wrongstatus(d, 34, 35, 46):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if wrongstatus(d, 47, 49, 56):
|
if common.wrongstatus(d, 47, 49, 56):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
ias = ias53(msg)
|
ias = ias53(msg)
|
||||||
@@ -77,23 +62,23 @@ def hdg53(msg):
|
|||||||
"""Magnetic heading, BDS 5,3 message
|
"""Magnetic heading, BDS 5,3 message
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (String): 28 bytes hexadecimal message (BDS53) string
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
float: angle in degrees to true north (from 0 to 360)
|
float: angle in degrees to true north (from 0 to 360)
|
||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = common.hex2bin(common.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 = common.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:
|
||||||
@@ -106,17 +91,17 @@ def ias53(msg):
|
|||||||
"""Indicated airspeed, DBS 5,3 message
|
"""Indicated airspeed, DBS 5,3 message
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (String): 28 bytes hexadecimal message
|
msg (str): 28 hexdigits
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
int: indicated arispeed in knots
|
int: indicated arispeed in knots
|
||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = common.hex2bin(common.data(msg))
|
||||||
|
|
||||||
if d[12] == '0':
|
if d[12] == "0":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
ias = bin2int(d[13:23]) # knots
|
ias = common.bin2int(d[13:23]) # knots
|
||||||
return ias
|
return ias
|
||||||
|
|
||||||
|
|
||||||
@@ -124,17 +109,17 @@ def mach53(msg):
|
|||||||
"""MACH number, DBS 5,3 message
|
"""MACH number, DBS 5,3 message
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (String): 28 bytes hexadecimal message
|
msg (str): 28 hexdigits
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
float: MACH number
|
float: MACH number
|
||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = common.hex2bin(common.data(msg))
|
||||||
|
|
||||||
if d[23] == '0':
|
if d[23] == "0":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
mach = bin2int(d[24:33]) * 0.008
|
mach = common.bin2int(d[24:33]) * 0.008
|
||||||
return round(mach, 3)
|
return round(mach, 3)
|
||||||
|
|
||||||
|
|
||||||
@@ -142,40 +127,41 @@ def tas53(msg):
|
|||||||
"""Aircraft true airspeed, BDS 5,3 message
|
"""Aircraft true airspeed, BDS 5,3 message
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (String): 28 bytes hexadecimal message
|
msg (str): 28 hexdigits
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
float: true airspeed in knots
|
float: true airspeed in knots
|
||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = common.hex2bin(common.data(msg))
|
||||||
|
|
||||||
if d[33] == '0':
|
if d[33] == "0":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
tas = bin2int(d[34:46]) * 0.5 # kts
|
tas = common.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
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (String): 28 bytes hexadecimal message (BDS60) string
|
msg (str): 28 hexdigits (BDS60) string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
int: vertical rate in feet/minutes
|
int: vertical rate in feet/minutes
|
||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = common.hex2bin(common.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 = common.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,56 +1,41 @@
|
|||||||
# 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/>.
|
|
||||||
|
|
||||||
from __future__ import absolute_import, print_function, division
|
|
||||||
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros, wrongstatus
|
|
||||||
|
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
# BDS 6,0
|
# BDS 6,0
|
||||||
# Heading and speed report
|
# Heading and speed report
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
|
|
||||||
|
from pyModeS import common
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (String): 28 bytes hexadecimal message string
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: True or False
|
bool: True or False
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if allzeros(msg):
|
if common.allzeros(msg):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
d = hex2bin(data(msg))
|
d = common.hex2bin(common.data(msg))
|
||||||
|
|
||||||
# status bit 1, 13, 24, 35, 46
|
# status bit 1, 13, 24, 35, 46
|
||||||
|
|
||||||
if wrongstatus(d, 1, 2, 12):
|
if common.wrongstatus(d, 1, 2, 12):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if wrongstatus(d, 13, 14, 23):
|
if common.wrongstatus(d, 13, 14, 23):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if wrongstatus(d, 24, 25, 34):
|
if common.wrongstatus(d, 24, 25, 34):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if wrongstatus(d, 35, 36, 45):
|
if common.wrongstatus(d, 35, 36, 45):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if wrongstatus(d, 46, 47, 56):
|
if common.wrongstatus(d, 46, 47, 56):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
ias = ias60(msg)
|
ias = ias60(msg)
|
||||||
@@ -76,18 +61,18 @@ def hdg60(msg):
|
|||||||
"""Megnetic heading of aircraft
|
"""Megnetic heading of aircraft
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (String): 28 bytes hexadecimal message (BDS60) string
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
float: heading in degrees to megnetic north (from 0 to 360)
|
float: heading in degrees to megnetic north (from 0 to 360)
|
||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = common.hex2bin(common.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 = common.bin2int(d[2:12])
|
||||||
|
|
||||||
if sign:
|
if sign:
|
||||||
value = value - 1024
|
value = value - 1024
|
||||||
@@ -105,17 +90,17 @@ def ias60(msg):
|
|||||||
"""Indicated airspeed
|
"""Indicated airspeed
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (String): 28 bytes hexadecimal message (BDS60) string
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
int: indicated airspeed in knots
|
int: indicated airspeed in knots
|
||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = common.hex2bin(common.data(msg))
|
||||||
|
|
||||||
if d[12] == '0':
|
if d[12] == "0":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
ias = bin2int(d[13:23]) # kts
|
ias = common.bin2int(d[13:23]) # kts
|
||||||
return ias
|
return ias
|
||||||
|
|
||||||
|
|
||||||
@@ -123,17 +108,17 @@ def mach60(msg):
|
|||||||
"""Aircraft MACH number
|
"""Aircraft MACH number
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (String): 28 bytes hexadecimal message (BDS60) string
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
float: MACH number
|
float: MACH number
|
||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = common.hex2bin(common.data(msg))
|
||||||
|
|
||||||
if d[23] == '0':
|
if d[23] == "0":
|
||||||
return None
|
return None
|
||||||
|
|
||||||
mach = bin2int(d[24:34]) * 2.048 / 512.0
|
mach = common.bin2int(d[24:34]) * 2.048 / 512.0
|
||||||
return round(mach, 3)
|
return round(mach, 3)
|
||||||
|
|
||||||
|
|
||||||
@@ -141,49 +126,49 @@ def vr60baro(msg):
|
|||||||
"""Vertical rate from barometric measurement, this value may be very noisy.
|
"""Vertical rate from barometric measurement, this value may be very noisy.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (String): 28 bytes hexadecimal message (BDS60) string
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
int: vertical rate in feet/minutes
|
int: vertical rate in feet/minutes
|
||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = common.hex2bin(common.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 = common.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
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
|
||||||
def vr60ins(msg):
|
def vr60ins(msg):
|
||||||
"""Vertical rate messured by onbard equiments (IRS, AHRS)
|
"""Vertical rate measurd by onbard equiments (IRS, AHRS)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (String): 28 bytes hexadecimal message (BDS60) string
|
msg (str): 28 hexdigits string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
int: vertical rate in feet/minutes
|
int: vertical rate in feet/minutes
|
||||||
"""
|
"""
|
||||||
d = hex2bin(data(msg))
|
d = common.hex2bin(common.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 = common.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
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
@@ -1,4 +1,26 @@
|
|||||||
from __future__ import absolute_import, print_function, division
|
"""Comm-B module.
|
||||||
|
|
||||||
|
The Comm-B module imports all functions from the following modules:
|
||||||
|
|
||||||
|
ELS - elementary surveillance
|
||||||
|
|
||||||
|
- pyModeS.decoder.bds.bds10
|
||||||
|
- pyModeS.decoder.bds.bds17
|
||||||
|
- pyModeS.decoder.bds.bds20
|
||||||
|
- pyModeS.decoder.bds.bds30
|
||||||
|
|
||||||
|
EHS - enhanced surveillance
|
||||||
|
|
||||||
|
- pyModeS.decoder.bds.bds40
|
||||||
|
- pyModeS.decoder.bds.bds50
|
||||||
|
- pyModeS.decoder.bds.bds60
|
||||||
|
|
||||||
|
MRAR and MHR
|
||||||
|
|
||||||
|
- pyModeS.decoder.bds.bds44
|
||||||
|
- pyModeS.decoder.bds.bds45
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
# ELS - elementary surveillance
|
# ELS - elementary surveillance
|
||||||
from pyModeS.decoder.bds.bds10 import *
|
from pyModeS.decoder.bds.bds10 import *
|
||||||
@@ -11,5 +33,6 @@ from pyModeS.decoder.bds.bds40 import *
|
|||||||
from pyModeS.decoder.bds.bds50 import *
|
from pyModeS.decoder.bds.bds50 import *
|
||||||
from pyModeS.decoder.bds.bds60 import *
|
from pyModeS.decoder.bds.bds60 import *
|
||||||
|
|
||||||
# MRAR
|
# MRAR and MHR
|
||||||
from pyModeS.decoder.bds.bds44 import *
|
from pyModeS.decoder.bds.bds44 import *
|
||||||
|
from pyModeS.decoder.bds.bds45 import *
|
||||||
|
|||||||
@@ -1,313 +0,0 @@
|
|||||||
from __future__ import absolute_import, print_function, division
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
def hex2bin(hexstr):
|
|
||||||
"""Convert a hexdecimal string to binary string, with zero fillings. """
|
|
||||||
num_of_bits = len(hexstr) * 4
|
|
||||||
binstr = bin(int(hexstr, 16))[2:].zfill(int(num_of_bits))
|
|
||||||
return binstr
|
|
||||||
|
|
||||||
|
|
||||||
def bin2int(binstr):
|
|
||||||
"""Convert a binary string to integer. """
|
|
||||||
return int(binstr, 2)
|
|
||||||
|
|
||||||
|
|
||||||
def hex2int(hexstr):
|
|
||||||
"""Convert a hexdecimal string to integer. """
|
|
||||||
return int(hexstr, 16)
|
|
||||||
|
|
||||||
|
|
||||||
def bin2np(binstr):
|
|
||||||
"""Convert a binary string to numpy array. """
|
|
||||||
return np.array([int(i) for i in binstr])
|
|
||||||
|
|
||||||
|
|
||||||
def np2bin(npbin):
|
|
||||||
"""Convert a binary numpy array to string. """
|
|
||||||
return np.array2string(npbin, separator='')[1:-1]
|
|
||||||
|
|
||||||
|
|
||||||
def df(msg):
|
|
||||||
"""Decode Downlink Format vaule, bits 1 to 5."""
|
|
||||||
msgbin = hex2bin(msg)
|
|
||||||
return min( bin2int(msgbin[0:5]) , 24 )
|
|
||||||
|
|
||||||
|
|
||||||
def crc(msg, encode=False):
|
|
||||||
"""Mode-S Cyclic Redundancy Check
|
|
||||||
Detect if bit error occurs in the Mode-S message
|
|
||||||
Args:
|
|
||||||
msg (string): 28 bytes hexadecimal message string
|
|
||||||
encode (bool): True to encode the date only and return the checksum
|
|
||||||
Returns:
|
|
||||||
string: message checksum, or partity bits (encoder)
|
|
||||||
"""
|
|
||||||
|
|
||||||
# the polynominal generattor code for CRC [1111111111111010000001001]
|
|
||||||
generator = np.array([1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,1,0,0,1])
|
|
||||||
ng = len(generator)
|
|
||||||
|
|
||||||
msgnpbin = bin2np(hex2bin(msg))
|
|
||||||
|
|
||||||
if encode:
|
|
||||||
msgnpbin[-24:] = [0] * 24
|
|
||||||
|
|
||||||
# loop all bits, except last 24 piraty bits
|
|
||||||
for i in range(len(msgnpbin)-24):
|
|
||||||
if msgnpbin[i] == 0:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# perform XOR, when 1
|
|
||||||
msgnpbin[i:i+ng] = np.bitwise_xor(msgnpbin[i:i+ng], generator)
|
|
||||||
|
|
||||||
# last 24 bits
|
|
||||||
reminder = np2bin(msgnpbin[-24:])
|
|
||||||
return reminder
|
|
||||||
|
|
||||||
|
|
||||||
def floor(x):
|
|
||||||
""" Mode-S floor function
|
|
||||||
|
|
||||||
Defined as the greatest integer value k, such that k <= x
|
|
||||||
|
|
||||||
eg.: floor(3.6) = 3, while floor(-3.6) = -4
|
|
||||||
"""
|
|
||||||
return int(np.floor(x))
|
|
||||||
|
|
||||||
|
|
||||||
def icao(msg):
|
|
||||||
"""Calculate the ICAO address from an Mode-S message
|
|
||||||
with DF4, DF5, DF20, DF21
|
|
||||||
|
|
||||||
Args:
|
|
||||||
msg (String): 28 bytes hexadecimal message string
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
String: ICAO address in 6 bytes hexadecimal string
|
|
||||||
"""
|
|
||||||
|
|
||||||
DF = df(msg)
|
|
||||||
|
|
||||||
if DF in (11, 17, 18):
|
|
||||||
addr = msg[2:8]
|
|
||||||
elif DF in (0, 4, 5, 16, 20, 21):
|
|
||||||
c0 = bin2int(crc(msg, encode=True))
|
|
||||||
c1 = hex2int(msg[-6:])
|
|
||||||
addr = '%06X' % (c0 ^ c1)
|
|
||||||
else:
|
|
||||||
addr = None
|
|
||||||
|
|
||||||
return addr
|
|
||||||
|
|
||||||
|
|
||||||
def is_icao_assigned(icao):
|
|
||||||
""" Check whether the ICAO address is assigned (Annex 10, Vol 3)"""
|
|
||||||
|
|
||||||
if (icao is None) or (not isinstance(icao, str)) or (len(icao)!=6):
|
|
||||||
return False
|
|
||||||
|
|
||||||
icaoint = hex2int(icao)
|
|
||||||
|
|
||||||
if 0x200000 < icaoint < 0x27FFFF: return False # AFI
|
|
||||||
if 0x280000 < icaoint < 0x28FFFF: return False # SAM
|
|
||||||
if 0x500000 < icaoint < 0x5FFFFF: return False # EUR, NAT
|
|
||||||
if 0x600000 < icaoint < 0x67FFFF: return False # MID
|
|
||||||
if 0x680000 < icaoint < 0x6F0000: return False # ASIA
|
|
||||||
if 0x900000 < icaoint < 0x9FFFFF: return False # NAM, PAC
|
|
||||||
if 0xB00000 < icaoint < 0xBFFFFF: return False # CAR
|
|
||||||
if 0xD00000 < icaoint < 0xDFFFFF: return False # future
|
|
||||||
if 0xF00000 < icaoint < 0xFFFFFF: return False # future
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def typecode(msg):
|
|
||||||
"""Type code of ADS-B message
|
|
||||||
|
|
||||||
Args:
|
|
||||||
msg (string): 28 bytes hexadecimal message string
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
int: type code number
|
|
||||||
"""
|
|
||||||
if df(msg) not in (17, 18):
|
|
||||||
return None
|
|
||||||
|
|
||||||
msgbin = hex2bin(msg)
|
|
||||||
return bin2int(msgbin[32:37])
|
|
||||||
|
|
||||||
|
|
||||||
def cprNL(lat):
|
|
||||||
"""NL() function in CPR decoding"""
|
|
||||||
|
|
||||||
if lat == 0:
|
|
||||||
return 59
|
|
||||||
|
|
||||||
if lat == 87 or lat == -87:
|
|
||||||
return 2
|
|
||||||
|
|
||||||
if lat > 87 or lat < -87:
|
|
||||||
return 1
|
|
||||||
|
|
||||||
nz = 15
|
|
||||||
a = 1 - np.cos(np.pi / (2 * nz))
|
|
||||||
b = np.cos(np.pi / 180.0 * abs(lat)) ** 2
|
|
||||||
nl = 2 * np.pi / (np.arccos(1 - a/b))
|
|
||||||
NL = floor(nl)
|
|
||||||
return NL
|
|
||||||
|
|
||||||
def idcode(msg):
|
|
||||||
"""Computes identity (squawk code) from DF5 or DF21 message, bit 20-32.
|
|
||||||
credit: @fbyrkjeland
|
|
||||||
|
|
||||||
Args:
|
|
||||||
msg (String): 28 bytes hexadecimal message string
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
string: squawk code
|
|
||||||
"""
|
|
||||||
|
|
||||||
if df(msg) not in [5, 21]:
|
|
||||||
raise RuntimeError("Message must be Downlink Format 5 or 21.")
|
|
||||||
|
|
||||||
mbin = hex2bin(msg)
|
|
||||||
|
|
||||||
C1 = mbin[19]
|
|
||||||
A1 = mbin[20]
|
|
||||||
C2 = mbin[21]
|
|
||||||
A2 = mbin[22]
|
|
||||||
C4 = mbin[23]
|
|
||||||
A4 = mbin[24]
|
|
||||||
# _ = mbin[25]
|
|
||||||
B1 = mbin[26]
|
|
||||||
D1 = mbin[27]
|
|
||||||
B2 = mbin[28]
|
|
||||||
D2 = mbin[29]
|
|
||||||
B4 = mbin[30]
|
|
||||||
D4 = mbin[31]
|
|
||||||
|
|
||||||
byte1 = int(A4+A2+A1, 2)
|
|
||||||
byte2 = int(B4+B2+B1, 2)
|
|
||||||
byte3 = int(C4+C2+C1, 2)
|
|
||||||
byte4 = int(D4+D2+D1, 2)
|
|
||||||
|
|
||||||
return str(byte1) + str(byte2) + str(byte3) + str(byte4)
|
|
||||||
|
|
||||||
|
|
||||||
def altcode(msg):
|
|
||||||
"""Computes the altitude from DF4 or DF20 message, bit 20-32.
|
|
||||||
credit: @fbyrkjeland
|
|
||||||
|
|
||||||
Args:
|
|
||||||
msg (String): 28 bytes hexadecimal message string
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
int: altitude in ft
|
|
||||||
"""
|
|
||||||
|
|
||||||
if df(msg) not in [0, 4, 16, 20]:
|
|
||||||
raise RuntimeError("Message must be Downlink Format 0, 4, 16, or 20.")
|
|
||||||
|
|
||||||
# Altitude code, bit 20-32
|
|
||||||
mbin = hex2bin(msg)
|
|
||||||
|
|
||||||
mbit = mbin[25] # M bit: 26
|
|
||||||
qbit = mbin[27] # Q bit: 28
|
|
||||||
|
|
||||||
|
|
||||||
if mbit == '0': # unit in ft
|
|
||||||
if qbit == '1': # 25ft interval
|
|
||||||
vbin = mbin[19:25] + mbin[26] + mbin[28:32]
|
|
||||||
alt = bin2int(vbin) * 25 - 1000
|
|
||||||
if qbit == '0': # 100ft interval, above 50175ft
|
|
||||||
C1 = mbin[19]
|
|
||||||
A1 = mbin[20]
|
|
||||||
C2 = mbin[21]
|
|
||||||
A2 = mbin[22]
|
|
||||||
C4 = mbin[23]
|
|
||||||
A4 = mbin[24]
|
|
||||||
# _ = mbin[25]
|
|
||||||
B1 = mbin[26]
|
|
||||||
# D1 = mbin[27] # always zero
|
|
||||||
B2 = mbin[28]
|
|
||||||
D2 = mbin[29]
|
|
||||||
B4 = mbin[30]
|
|
||||||
D4 = mbin[31]
|
|
||||||
|
|
||||||
graystr = D2 + D4 + A1 + A2 + A4 + B1 + B2 + B4 + C1 + C2 + C4
|
|
||||||
alt = gray2alt(graystr)
|
|
||||||
|
|
||||||
if mbit == '1': # unit in meter
|
|
||||||
vbin = mbin[19:25] + mbin[26:31]
|
|
||||||
alt = int(bin2int(vbin) * 3.28084) # convert to ft
|
|
||||||
|
|
||||||
return alt
|
|
||||||
|
|
||||||
|
|
||||||
def gray2alt(codestr):
|
|
||||||
gc500 = codestr[:8]
|
|
||||||
n500 = gray2int(gc500)
|
|
||||||
|
|
||||||
# in 100-ft step must be converted first
|
|
||||||
gc100 = codestr[8:]
|
|
||||||
n100 = gray2int(gc100)
|
|
||||||
|
|
||||||
if n100 in [0, 5, 6]:
|
|
||||||
return None
|
|
||||||
|
|
||||||
if n100 == 7:
|
|
||||||
n100 = 5
|
|
||||||
|
|
||||||
if n500%2:
|
|
||||||
n100 = 6 - n100
|
|
||||||
|
|
||||||
alt = (n500*500 + n100*100) - 1300
|
|
||||||
return alt
|
|
||||||
|
|
||||||
|
|
||||||
def gray2int(graystr):
|
|
||||||
"""Convert greycode to binary"""
|
|
||||||
num = bin2int(graystr)
|
|
||||||
num ^= (num >> 8)
|
|
||||||
num ^= (num >> 4)
|
|
||||||
num ^= (num >> 2)
|
|
||||||
num ^= (num >> 1)
|
|
||||||
return num
|
|
||||||
|
|
||||||
|
|
||||||
def data(msg):
|
|
||||||
"""Return the data frame in the message, bytes 9 to 22"""
|
|
||||||
return msg[8:-6]
|
|
||||||
|
|
||||||
|
|
||||||
def allzeros(msg):
|
|
||||||
"""check if the data bits are all zeros
|
|
||||||
|
|
||||||
Args:
|
|
||||||
msg (String): 28 bytes hexadecimal message string
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: True or False
|
|
||||||
"""
|
|
||||||
d = hex2bin(data(msg))
|
|
||||||
|
|
||||||
if bin2int(d) > 0:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def wrongstatus(data, sb, msb, lsb):
|
|
||||||
"""Check if the status bit and field bits are consistency. This Function
|
|
||||||
is used for checking BDS code versions.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# status bit, most significant bit, least significant bit
|
|
||||||
status = int(data[sb-1])
|
|
||||||
value = bin2int(data[msb-1:lsb])
|
|
||||||
|
|
||||||
if not status:
|
|
||||||
if value != 0:
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
@@ -1,4 +1,14 @@
|
|||||||
from __future__ import absolute_import, print_function, division
|
"""EHS Wrapper.
|
||||||
|
|
||||||
|
``pyModeS.ehs`` is deprecated, please use ``pyModeS.commb`` instead.
|
||||||
|
|
||||||
|
The EHS wrapper imports all functions from the following modules:
|
||||||
|
- pyModeS.decoder.bds.bds40
|
||||||
|
- pyModeS.decoder.bds.bds50
|
||||||
|
- pyModeS.decoder.bds.bds60
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
from pyModeS.decoder.bds.bds40 import *
|
from pyModeS.decoder.bds.bds40 import *
|
||||||
@@ -6,13 +16,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)
|
||||||
|
|||||||
@@ -1,4 +1,14 @@
|
|||||||
from __future__ import absolute_import, print_function, division
|
"""ELS Wrapper.
|
||||||
|
|
||||||
|
``pyModeS.els`` is deprecated, please use ``pyModeS.commb`` instead.
|
||||||
|
|
||||||
|
The ELS wrapper imports all functions from the following modules:
|
||||||
|
- pyModeS.decoder.bds.bds10
|
||||||
|
- pyModeS.decoder.bds.bds17
|
||||||
|
- pyModeS.decoder.bds.bds20
|
||||||
|
- pyModeS.decoder.bds.bds30
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
from pyModeS.decoder.bds.bds10 import *
|
from pyModeS.decoder.bds.bds10 import *
|
||||||
from pyModeS.decoder.bds.bds17 import *
|
from pyModeS.decoder.bds.bds17 import *
|
||||||
@@ -6,5 +16,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,21 +1,5 @@
|
|||||||
# 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
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, print_function, division
|
[To be implemented]
|
||||||
from pyModeS.decoder import common
|
"""
|
||||||
|
|||||||
@@ -1,120 +1,152 @@
|
|||||||
|
"""Uncertainty parameters.
|
||||||
|
|
||||||
|
See source code at: https://github.com/junzis/pyModeS/blob/master/pyModeS/decoder/uncertainty.py
|
||||||
|
"""
|
||||||
|
|
||||||
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}},
|
|
||||||
}
|
}
|
||||||
|
|||||||
25
pyModeS/decoder/uplink.py
Normal file
25
pyModeS/decoder/uplink.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
from pyModeS import common
|
||||||
|
|
||||||
|
|
||||||
|
def uplink_icao(msg):
|
||||||
|
"""Calculate the ICAO address from a Mode-S interrogation (uplink message)"""
|
||||||
|
p_gen = 0xFFFA0480 << ((len(msg) - 14) * 4)
|
||||||
|
data = int(msg[:-6], 16)
|
||||||
|
PA = int(msg[-6:], 16)
|
||||||
|
ad = 0
|
||||||
|
topbit = 0b1 << (len(msg) * 4 - 25)
|
||||||
|
for j in range(0, len(msg) * 4, 1):
|
||||||
|
if data & topbit:
|
||||||
|
data ^= p_gen
|
||||||
|
data = (data << 1) + ((PA >> 23) & 1)
|
||||||
|
PA = PA << 1
|
||||||
|
if j > (len(msg) * 4 - 26):
|
||||||
|
ad = ad + ((data >> (len(msg) * 4 - 25)) & 1)
|
||||||
|
ad = ad << 1
|
||||||
|
return "%06X" % (ad >> 2)
|
||||||
|
|
||||||
|
|
||||||
|
def uf(msg):
|
||||||
|
"""Decode Uplink Format value, bits 1 to 5."""
|
||||||
|
ufbin = common.hex2bin(msg[:2])
|
||||||
|
return min(common.bin2int(ufbin[0:5]), 24)
|
||||||
68
pyModeS/encoder/__init__.py
Normal file
68
pyModeS/encoder/__init__.py
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
from .bds.bds08 import me08
|
||||||
|
from .bds.bds09 import me09
|
||||||
|
from pyModeS import common
|
||||||
|
|
||||||
|
|
||||||
|
def encode_adsb(**kwargs):
|
||||||
|
"""Encode ADS-B message.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
icao (string): Transponder ICAO address (6 hexdigits)
|
||||||
|
capability (int): Transponder capability, between 0 and 7
|
||||||
|
typecode (int): Typecode, less than 32
|
||||||
|
|
||||||
|
callsign (string): Callsign (6 hexdigits)
|
||||||
|
category (int): Aircraft category, between 0 and 7, Default to 0.
|
||||||
|
|
||||||
|
speed (int): Speed in knots.
|
||||||
|
angle (float): Track angle or heading angle in degrees.
|
||||||
|
vertical_rate (int): vertical rate in feet/minute
|
||||||
|
intent_change (int): Intent change flag, 0 or 1. Default to 0.
|
||||||
|
ifr_capability (int): IFR capability flag, 0 or 1. Default to 1.
|
||||||
|
navigation_quality (int): NUC (ver 0) or NACv (ver 1, 2), between 0 and 7.
|
||||||
|
Default to 0.
|
||||||
|
supersonic (bool): Is this a supersonic flight? Default to False.
|
||||||
|
speed_type (str): Speed type: GS, IAS, or TAS. Default to GS.
|
||||||
|
vertical_rate_source (str): GNSS or BARO. Default to BARO.
|
||||||
|
gnss_baro_alt_diff (int): Different between GNSS and barometric altitude in feet.
|
||||||
|
Negative value indicates GNSS altitude below barometric altitude. Default to 0
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
string: 28 hexdigits raw message
|
||||||
|
|
||||||
|
"""
|
||||||
|
tc = kwargs.get("typecode")
|
||||||
|
|
||||||
|
if 1 <= tc <= 4:
|
||||||
|
me = me08(**kwargs)
|
||||||
|
elif tc == 19:
|
||||||
|
me = me09(**kwargs)
|
||||||
|
|
||||||
|
msg = _constuct(**dict(kwargs, me=me))
|
||||||
|
return msg
|
||||||
|
|
||||||
|
|
||||||
|
def _constuct(**kwargs):
|
||||||
|
icao = kwargs.get("icao")
|
||||||
|
me = kwargs.get("me")
|
||||||
|
capability = kwargs.get("capability", 6)
|
||||||
|
|
||||||
|
if icao is None or len(icao) != 6:
|
||||||
|
raise Exception("Transponder address must be 6 hexadecimal characters.")
|
||||||
|
|
||||||
|
if me is None or len(me) != 14:
|
||||||
|
raise Exception("Message be 14 hexadecimal characters.")
|
||||||
|
|
||||||
|
if capability > 6:
|
||||||
|
raise Exception("Transponder capability must be smaller than 7.")
|
||||||
|
|
||||||
|
header_bin = "10001" + "{0:03b}".format(capability)
|
||||||
|
header_hex = "{0:02X}".format(int(header_bin, 2))
|
||||||
|
|
||||||
|
msg = header_hex + icao + me + "000000"
|
||||||
|
|
||||||
|
pi = common.crc(msg, encode=True)
|
||||||
|
pi_hex = "{0:06X}".format(pi)
|
||||||
|
|
||||||
|
msg = msg[:-6] + pi_hex
|
||||||
|
return msg
|
||||||
0
pyModeS/encoder/bds/__init__.py
Normal file
0
pyModeS/encoder/bds/__init__.py
Normal file
5
pyModeS/encoder/bds/bds05.py
Normal file
5
pyModeS/encoder/bds/bds05.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# ------------------------------------------
|
||||||
|
# BDS 0,5
|
||||||
|
# ADS-B TC=9-18
|
||||||
|
# Airborn position
|
||||||
|
# ------------------------------------------
|
||||||
5
pyModeS/encoder/bds/bds06.py
Normal file
5
pyModeS/encoder/bds/bds06.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# ------------------------------------------
|
||||||
|
# BDS 0,6
|
||||||
|
# ADS-B TC=5-8
|
||||||
|
# Surface position
|
||||||
|
# ------------------------------------------
|
||||||
40
pyModeS/encoder/bds/bds08.py
Normal file
40
pyModeS/encoder/bds/bds08.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# ------------------------------------------
|
||||||
|
# BDS 0,8
|
||||||
|
# ADS-B TC=1-4
|
||||||
|
# Aircraft identitification and category
|
||||||
|
# ------------------------------------------
|
||||||
|
|
||||||
|
from pyModeS import common
|
||||||
|
|
||||||
|
charmap = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ##### ###############0123456789######"
|
||||||
|
|
||||||
|
|
||||||
|
def me08(callsign, **kwargs):
|
||||||
|
cs = callsign
|
||||||
|
tc = kwargs.get("typecode")
|
||||||
|
cat = kwargs.get("category", 0)
|
||||||
|
|
||||||
|
if len(cs) > 8:
|
||||||
|
raise Exception("callsign must contain less than 9 characters")
|
||||||
|
|
||||||
|
if tc > 4:
|
||||||
|
raise Exception("typecode must be less 5")
|
||||||
|
|
||||||
|
if cat > 7:
|
||||||
|
raise Exception("category must be less 8")
|
||||||
|
|
||||||
|
if not cs.isalnum():
|
||||||
|
raise Exception("callsign must only contain alphanumeric characters")
|
||||||
|
|
||||||
|
cs = "{:<8}".format(cs.upper())
|
||||||
|
|
||||||
|
idx = [charmap.index(c) for c in cs]
|
||||||
|
me_bin = (
|
||||||
|
"{0:05b}".format(tc)
|
||||||
|
+ "{0:03b}".format(cat)
|
||||||
|
+ "".join("{0:06b}".format(i) for i in idx)
|
||||||
|
)
|
||||||
|
|
||||||
|
me_hex = "{0:04X}".format(int(me_bin, 2))
|
||||||
|
|
||||||
|
return me_hex
|
||||||
119
pyModeS/encoder/bds/bds09.py
Normal file
119
pyModeS/encoder/bds/bds09.py
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
# ------------------------------------------
|
||||||
|
# BDS 0,9
|
||||||
|
# ADS-B TC=19
|
||||||
|
# Aircraft Airborn velocity
|
||||||
|
# ------------------------------------------
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
def me09(speed, angle, vertical_rate, **kwargs):
|
||||||
|
spd = speed
|
||||||
|
agl = angle
|
||||||
|
vr = vertical_rate
|
||||||
|
|
||||||
|
tc = kwargs.get("typecode")
|
||||||
|
intent = kwargs.get("intent_change", 0)
|
||||||
|
ifr = kwargs.get("ifr_capability", 1)
|
||||||
|
navq = kwargs.get("navigation_quality", 0)
|
||||||
|
supersonic = kwargs.get("supersonic", False)
|
||||||
|
spd_type = kwargs.get("speed_type", "gs").lower()
|
||||||
|
vr_source = kwargs.get("vertical_rate_source", "baro").lower()
|
||||||
|
alt_diff = kwargs.get("gnss_baro_alt_diff", 0)
|
||||||
|
|
||||||
|
if tc != 19:
|
||||||
|
raise Exception("Typecode must be 19.")
|
||||||
|
|
||||||
|
if intent not in (0, 1):
|
||||||
|
raise Exception("Intent change flag must be 0 or 1.")
|
||||||
|
|
||||||
|
if ifr not in (0, 1):
|
||||||
|
raise Exception("IFR capability flag must be 0 or 1.")
|
||||||
|
|
||||||
|
if type(supersonic) != bool:
|
||||||
|
raise Exception("Subsonic flag must be True or False.")
|
||||||
|
|
||||||
|
if navq > 7:
|
||||||
|
raise Exception("Navigation quality indicator must be smaller than 8.")
|
||||||
|
|
||||||
|
if spd_type not in ["gs", "tas"]:
|
||||||
|
raise Exception("Speed type must be 'gs', 'ias', or 'tas'.")
|
||||||
|
|
||||||
|
if vr_source not in ["baro", "gnss"]:
|
||||||
|
raise Exception("Vertical rate source must be 'baro' or 'gnss'.")
|
||||||
|
|
||||||
|
me_bin = ""
|
||||||
|
|
||||||
|
# typecode
|
||||||
|
me_bin += "{0:05b}".format(tc)
|
||||||
|
|
||||||
|
# sub-type
|
||||||
|
if supersonic:
|
||||||
|
if spd_type == "gs":
|
||||||
|
me_bin += "010"
|
||||||
|
else:
|
||||||
|
me_bin += "100"
|
||||||
|
else:
|
||||||
|
if spd_type == "gs":
|
||||||
|
me_bin += "001"
|
||||||
|
else:
|
||||||
|
me_bin += "011"
|
||||||
|
|
||||||
|
# intent, ifr, navigation quality
|
||||||
|
me_bin += str(intent) + str(ifr) + "{0:03b}".format(navq)
|
||||||
|
|
||||||
|
# speed and angle part
|
||||||
|
if spd_type == "gs":
|
||||||
|
vx = spd * np.sin(np.radians(agl))
|
||||||
|
vy = spd * np.cos(np.radians(agl))
|
||||||
|
|
||||||
|
if supersonic:
|
||||||
|
vx /= 4
|
||||||
|
vy /= 4
|
||||||
|
|
||||||
|
vx = int(round(vx))
|
||||||
|
vy = int(round(vy))
|
||||||
|
|
||||||
|
sew = "0" if vx >= 0 else "1"
|
||||||
|
sns = "0" if vy >= 0 else "1"
|
||||||
|
vew = "{0:010b}".format(min(abs(vx), 1023) + 1)
|
||||||
|
vns = "{0:010b}".format(min(abs(vy), 1023) + 1)
|
||||||
|
|
||||||
|
me_bin += sew + vew + sns + vns
|
||||||
|
|
||||||
|
elif spd_type == "ias" or spd_type == "tas":
|
||||||
|
hdg = int(round(agl * 1024 / 360))
|
||||||
|
hdg = min(hdg, 1023)
|
||||||
|
|
||||||
|
air_type = "1" if spd_type == "tas" else "0"
|
||||||
|
|
||||||
|
if supersonic:
|
||||||
|
spd /= 4
|
||||||
|
|
||||||
|
spd = min(int(round(spd)), 1023)
|
||||||
|
|
||||||
|
me_bin += "1" + "{0:010b}".format(hdg) + air_type + "{0:010b}".format(spd)
|
||||||
|
|
||||||
|
# vertical rate source
|
||||||
|
me_bin += "1" if vr_source == "baro" else "0"
|
||||||
|
|
||||||
|
# vertical rate
|
||||||
|
me_bin += "0" if vr > 0 else "1"
|
||||||
|
vr = int(round((abs(vr) / 64 + 1)))
|
||||||
|
vr = min(vr, 511)
|
||||||
|
me_bin += "{0:09b}".format(vr)
|
||||||
|
|
||||||
|
# reserved
|
||||||
|
me_bin += "00"
|
||||||
|
|
||||||
|
# altitude difference
|
||||||
|
me_bin += "1" if alt_diff < 0 else "0"
|
||||||
|
alt_diff = int(round(abs(alt_diff) / 25 + 1))
|
||||||
|
alt_diff = min(alt_diff, 127)
|
||||||
|
me_bin += "{0:07b}".format(alt_diff)
|
||||||
|
print(me_bin)
|
||||||
|
|
||||||
|
# convert to hexdigits
|
||||||
|
me_hex = "{0:04X}".format(int(me_bin, 2))
|
||||||
|
|
||||||
|
return me_hex
|
||||||
@@ -1 +0,0 @@
|
|||||||
from __future__ import absolute_import, print_function, division
|
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
"""
|
"""
|
||||||
Functions for aeronautics in this module
|
Functions for aeronautics in this module
|
||||||
- physical quantities always in SI units
|
|
||||||
- lat,lon,course and heading in degrees
|
- physical quantities always in SI units
|
||||||
|
- lat,lon,course and heading in degrees
|
||||||
|
|
||||||
International Standard Atmosphere
|
International Standard Atmosphere
|
||||||
|
::
|
||||||
|
|
||||||
p,rho,T = atmos(H) # atmos as function of geopotential altitude H [m]
|
p,rho,T = atmos(H) # atmos as function of geopotential altitude H [m]
|
||||||
a = vsound(H) # speed of sound [m/s] as function of H[m]
|
a = vsound(H) # speed of sound [m/s] as function of H[m]
|
||||||
p = pressure(H) # calls atmos but retruns only pressure [Pa]
|
p = pressure(H) # calls atmos but returns only pressure [Pa]
|
||||||
T = temperature(H) # calculates temperature [K]
|
T = temperature(H) # calculates temperature [K]
|
||||||
rho = density(H) # calls atmos but retruns only pressure [Pa]
|
rho = density(H) # calls atmos but returns only pressure [Pa]
|
||||||
|
|
||||||
|
Speed conversion at altitude H[m] in ISA
|
||||||
|
::
|
||||||
|
|
||||||
Speed conversion at altitude H[m] in ISA:
|
|
||||||
Mach = tas2mach(Vtas,H) # true airspeed (Vtas) to mach number conversion
|
Mach = tas2mach(Vtas,H) # true airspeed (Vtas) to mach number conversion
|
||||||
Vtas = mach2tas(Mach,H) # true airspeed (Vtas) to mach number conversion
|
Vtas = mach2tas(Mach,H) # true airspeed (Vtas) to mach number conversion
|
||||||
Vtas = eas2tas(Veas,H) # equivalent airspeed to true airspeed, H in [m]
|
Vtas = eas2tas(Veas,H) # equivalent airspeed to true airspeed, H in [m]
|
||||||
@@ -19,36 +24,37 @@ Speed conversion at altitude H[m] in ISA:
|
|||||||
Vcas = tas2cas(Vtas,H) # Vtas to Vcas conversion both m/s, H in [m]
|
Vcas = tas2cas(Vtas,H) # Vtas to Vcas conversion both m/s, H in [m]
|
||||||
Vcas = mach2cas(Mach,H) # Mach to Vcas conversion Vcas in m/s, H in [m]
|
Vcas = mach2cas(Mach,H) # Mach to Vcas conversion Vcas in m/s, H in [m]
|
||||||
Mach = cas2mach(Vcas,H) # Vcas to mach copnversion Vcas in m/s, H in [m]
|
Mach = cas2mach(Vcas,H) # Vcas to mach copnversion Vcas in m/s, H in [m]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
"""Aero and geo Constants """
|
"""Aero and geo Constants """
|
||||||
kts = 0.514444 # knot -> m/s
|
kts = 0.514444 # knot -> m/s
|
||||||
ft = 0.3048 # ft -> m
|
ft = 0.3048 # ft -> m
|
||||||
fpm = 0.00508 # ft/min -> m/s
|
fpm = 0.00508 # ft/min -> m/s
|
||||||
inch = 0.0254 # inch -> m
|
inch = 0.0254 # inch -> m
|
||||||
sqft = 0.09290304 # 1 square foot
|
sqft = 0.09290304 # 1 square foot
|
||||||
nm = 1852. # nautical mile -> m
|
nm = 1852.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
|
||||||
@@ -95,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
|
||||||
|
|
||||||
|
|
||||||
@@ -108,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
|
||||||
@@ -123,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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
188
pyModeS/extra/rtlreader.py
Normal file
188
pyModeS/extra/rtlreader.py
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
import traceback
|
||||||
|
import numpy as np
|
||||||
|
import pyModeS as pms
|
||||||
|
from rtlsdr import RtlSdr
|
||||||
|
import time
|
||||||
|
|
||||||
|
sampling_rate = 2e6
|
||||||
|
smaples_per_microsec = 2
|
||||||
|
|
||||||
|
modes_frequency = 1090e6
|
||||||
|
buffer_size = 1024 * 200
|
||||||
|
read_size = 1024 * 100
|
||||||
|
|
||||||
|
pbits = 8
|
||||||
|
fbits = 112
|
||||||
|
preamble = [1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0]
|
||||||
|
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 = [] # amplitude of the sample only
|
||||||
|
self.sdr = RtlSdr()
|
||||||
|
self.sdr.sample_rate = sampling_rate
|
||||||
|
self.sdr.center_freq = modes_frequency
|
||||||
|
self.sdr.gain = "auto"
|
||||||
|
|
||||||
|
self.debug = kwargs.get("debug", False)
|
||||||
|
self.raw_pipe_in = None
|
||||||
|
self.stop_flag = False
|
||||||
|
self.noise_floor = 1e6
|
||||||
|
|
||||||
|
def _calc_noise(self):
|
||||||
|
"""Calculate noise floor"""
|
||||||
|
window = smaples_per_microsec * 100
|
||||||
|
total_len = len(self.signal_buffer)
|
||||||
|
means = (
|
||||||
|
np.array(self.signal_buffer[: total_len // window * window])
|
||||||
|
.reshape(-1, window)
|
||||||
|
.mean(axis=1)
|
||||||
|
)
|
||||||
|
return min(means)
|
||||||
|
|
||||||
|
def _process_buffer(self):
|
||||||
|
"""process raw IQ data in the buffer"""
|
||||||
|
|
||||||
|
# update noise floor
|
||||||
|
self.noise_floor = min(self._calc_noise(), self.noise_floor)
|
||||||
|
|
||||||
|
# set minimum signal amplitude
|
||||||
|
min_sig_amp = 3.162 * self.noise_floor # 10 dB SNR
|
||||||
|
|
||||||
|
# Mode S messages
|
||||||
|
messages = []
|
||||||
|
|
||||||
|
buffer_length = len(self.signal_buffer)
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
while i < buffer_length:
|
||||||
|
if self.signal_buffer[i] < min_sig_amp:
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
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]
|
||||||
|
|
||||||
|
threshold = max(frame_pulses) * 0.2
|
||||||
|
|
||||||
|
msgbin = []
|
||||||
|
for j in range(0, frame_length, 2):
|
||||||
|
p2 = frame_pulses[j : j + 2]
|
||||||
|
if len(p2) < 2:
|
||||||
|
break
|
||||||
|
|
||||||
|
if p2[0] < threshold and p2[1] < threshold:
|
||||||
|
break
|
||||||
|
elif p2[0] >= p2[1]:
|
||||||
|
c = 1
|
||||||
|
elif p2[0] < p2[1]:
|
||||||
|
c = 0
|
||||||
|
else:
|
||||||
|
msgbin = []
|
||||||
|
break
|
||||||
|
|
||||||
|
msgbin.append(c)
|
||||||
|
|
||||||
|
# advance i with a jump
|
||||||
|
i = frame_start + j
|
||||||
|
|
||||||
|
if len(msgbin) > 0:
|
||||||
|
msghex = pms.bin2hex("".join([str(i) for i in 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
|
||||||
|
|
||||||
|
# reset the buffer
|
||||||
|
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):
|
||||||
|
amp = np.absolute(data)
|
||||||
|
self.signal_buffer.extend(amp.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.close()
|
||||||
|
|
||||||
|
def run(self, raw_pipe_in=None, stop_flag=None, exception_queue=None):
|
||||||
|
self.raw_pipe_in = raw_pipe_in
|
||||||
|
self.stop_flag = stop_flag
|
||||||
|
|
||||||
|
try:
|
||||||
|
# raise RuntimeError("test exception")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
data = self.sdr.read_samples(read_size)
|
||||||
|
self._read_callback(data, None)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
tb = traceback.format_exc()
|
||||||
|
if exception_queue is not None:
|
||||||
|
exception_queue.put(tb)
|
||||||
|
raise e
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import signal
|
||||||
|
|
||||||
|
rtl = RtlReader()
|
||||||
|
signal.signal(signal.SIGINT, rtl.stop)
|
||||||
|
|
||||||
|
rtl.debug = True
|
||||||
|
rtl.run()
|
||||||
@@ -1,53 +1,54 @@
|
|||||||
'''
|
"""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
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import socket
|
|
||||||
import time
|
import time
|
||||||
from threading import Thread
|
import pyModeS as pms
|
||||||
|
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
|
||||||
@@ -55,9 +56,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 = []
|
||||||
@@ -65,7 +66,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,
|
||||||
@@ -79,8 +81,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
|
||||||
@@ -89,16 +90,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 = []
|
||||||
@@ -110,12 +111,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 = []
|
||||||
|
|
||||||
@@ -129,67 +130,83 @@ 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]:
|
if len(msg) not in [14, 28]:
|
||||||
# incomplete message
|
continue
|
||||||
|
|
||||||
|
df = pms.df(msg)
|
||||||
|
|
||||||
|
# skip incomplete message
|
||||||
|
if df in [0, 4, 5, 11] and len(msg) != 14:
|
||||||
|
continue
|
||||||
|
if df in [16, 17, 18, 19, 20, 21, 24] and len(msg) != 28:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
messages.append([msg, ts])
|
messages.append([msg, ts])
|
||||||
return messages
|
return messages
|
||||||
|
|
||||||
def read_skysense_buffer(self):
|
def read_skysense_buffer(self):
|
||||||
"""
|
"""Skysense stream format.
|
||||||
----------------------------------------------------------------------------------
|
|
||||||
Field SS MS MS MS MS MS MS MS MS MS MS MS MS MS MS TS TS TS TS TS TS RS RS RS
|
::
|
||||||
----------------------------------------------------------------------------------
|
|
||||||
Position: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
----------------------------------------------------------------------------------
|
||||||
----------------------------------------------------------------------------------
|
Field SS MS MS MS MS MS MS MS MS MS MS MS MS MS MS TS TS TS TS TS TS RS RS RS
|
||||||
|
----------------------------------------------------------------------------------
|
||||||
SS field - Start character
|
Position: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
||||||
Position 0:
|
----------------------------------------------------------------------------------
|
||||||
1 byte = 8 bits
|
|
||||||
Start character '$'
|
SS field - Start character
|
||||||
|
Position 0:
|
||||||
MS field - Payload
|
1 byte = 8 bits
|
||||||
Postion 1 through 14:
|
Start character '$'
|
||||||
14 bytes = 112 bits
|
|
||||||
Mode-S payload
|
MS field - Payload
|
||||||
In case of DF types that only carry 7 bytes of information position 8 through 14 are set to 0x00.
|
Position 1 through 14:
|
||||||
|
14 bytes = 112 bits
|
||||||
TS field - Time stamp
|
Mode-S payload
|
||||||
Position 15 through 20:
|
In case of DF types that only carry 7 bytes of information
|
||||||
6 bytes = 48 bits
|
position 8 through 14 are set to 0x00.
|
||||||
Time stamp with fields as:
|
|
||||||
|
TS field - Time stamp
|
||||||
Lock Status - Status of internal time keeping mechanism
|
Position 15 through 20:
|
||||||
Equal to 1 if operating normally
|
6 bytes = 48 bits
|
||||||
Bit 47 - 1 bit
|
Time stamp with fields as:
|
||||||
|
|
||||||
Time of day in UTC seconds, between 0 and 86399
|
Lock Status - Status of internal time keeping mechanism
|
||||||
Bits 46 through 30 - 17 bits
|
Equal to 1 if operating normally
|
||||||
|
Bit 47 - 1 bit
|
||||||
Nanoseconds into current second, between 0 and 999999999
|
|
||||||
Bits 29 through 0 - 30 bits
|
Time of day in UTC seconds, between 0 and 86399
|
||||||
|
Bits 46 through 30 - 17 bits
|
||||||
RS field - Signal Level
|
|
||||||
Position 21 through 23:
|
Nanoseconds into current second, between 0 and 999999999
|
||||||
3 bytes = 24 bits
|
Bits 29 through 0 - 30 bits
|
||||||
RSSI (received signal strength indication) and relative noise level with fields
|
|
||||||
|
RS field - Signal Level
|
||||||
RNL, Q12.4 unsigned fixed point binary with 4 fractional bits and 8 integer bits.
|
Position 21 through 23:
|
||||||
This is and indication of the noise level of the message. Roughly 40 counts per 10dBm.
|
3 bytes = 24 bits
|
||||||
Bits 23 through 12 - 12 bits
|
RSSI (received signal strength indication) and relative
|
||||||
|
noise level with fields
|
||||||
RSSI, Q12.4 unsigned fixed point binary with 4 fractional bits and 8 integer bits.
|
|
||||||
This is an indication of the signal level of the received message in ADC counts. Roughly 40 counts per 10dBm.
|
RNL, Q12.4 unsigned fixed point binary with 4 fractional
|
||||||
Bits 11 through 0 - 12 bits
|
bits and 8 integer bits.
|
||||||
|
This is and indication of the noise level of the message.
|
||||||
|
Roughly 40 counts per 10dBm.
|
||||||
|
Bits 23 through 12 - 12 bits
|
||||||
|
|
||||||
|
RSSI, Q12.4 unsigned fixed point binary with 4 fractional
|
||||||
|
bits and 8 integer bits.
|
||||||
|
This is an indication of the signal level of the received
|
||||||
|
message in ADC counts. Roughly 40 counts per 10dBm.
|
||||||
|
Bits 11 through 0 - 12 bits
|
||||||
"""
|
"""
|
||||||
SS_MSGLENGTH = 24
|
SS_MSGLENGTH = 24
|
||||||
SS_STARTCHAR = 0x24
|
SS_STARTCHAR = 0x24
|
||||||
@@ -200,27 +217,35 @@ 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
|
||||||
|
|
||||||
def handle_messages(self, messages):
|
def handle_messages(self, messages):
|
||||||
@@ -228,12 +253,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, exception_queue=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]
|
||||||
@@ -241,16 +268,11 @@ class BaseClient(Thread):
|
|||||||
self.buffer.extend(received)
|
self.buffer.extend(received)
|
||||||
# print(''.join(x.encode('hex') for x in self.buffer))
|
# print(''.join(x.encode('hex') for x in self.buffer))
|
||||||
|
|
||||||
# process self.buffer when it is longer enough
|
if self.datatype == "beast":
|
||||||
# if len(self.buffer) < 2048:
|
|
||||||
# continue
|
|
||||||
# -- Removed!! Cause delay in low data rate scenario --
|
|
||||||
|
|
||||||
if self.rawtype == '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:
|
||||||
@@ -258,21 +280,18 @@ class BaseClient(Thread):
|
|||||||
else:
|
else:
|
||||||
self.handle_messages(messages)
|
self.handle_messages(messages)
|
||||||
|
|
||||||
time.sleep(0.001)
|
# raise RuntimeError("test exception")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Unexpected Error:", e)
|
tb = traceback.format_exc()
|
||||||
|
exception_queue.put(tb)
|
||||||
try:
|
raise e
|
||||||
sock = self.connect()
|
|
||||||
except Exception as 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()
|
||||||
|
|||||||
288
pyModeS/streamer/decode.py
Normal file
288
pyModeS/streamer/decode.py
Normal file
@@ -0,0 +1,288 @@
|
|||||||
|
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 received 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 memory"""
|
||||||
|
acs = self.acs
|
||||||
|
return acs
|
||||||
|
|
||||||
|
def run(self, raw_pipe_out, ac_pipe_in, exception_queue):
|
||||||
|
local_buffer = []
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
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)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
tb = traceback.format_exc()
|
||||||
|
exception_queue.put((e, tb))
|
||||||
@@ -1,120 +1,154 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
from __future__ import print_function, division
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
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)
|
support_rawtypes = ["raw", "beast", "skysense"]
|
||||||
client.daemon = True
|
|
||||||
client.start()
|
|
||||||
|
|
||||||
stream = Stream(lat0=LAT0, lon0=LON0, dumpto=DUMPTO)
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument(
|
||||||
|
"--source",
|
||||||
|
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()
|
||||||
|
|
||||||
try:
|
SOURCE = args.source
|
||||||
screen = Screen(uncertainty=UNCERTAINTY)
|
LATLON = args.latlon
|
||||||
screen.daemon = True
|
UNCERTAINTY = args.uncertainty
|
||||||
screen.start()
|
DUMPTO = args.dumpto
|
||||||
|
|
||||||
while True:
|
if SOURCE == "rtlsdr":
|
||||||
if len(ADSB_MSG) > 200:
|
pass
|
||||||
LOCK.acquire()
|
elif SOURCE == "net":
|
||||||
stream.process_raw(ADSB_TS, ADSB_MSG, COMMB_TS, COMMB_MSG)
|
if args.connect is None:
|
||||||
ADSB_MSG = []
|
print("Error: --connect argument must not be empty.")
|
||||||
ADSB_TS = []
|
else:
|
||||||
COMMB_MSG = []
|
SERVER, PORT, DATATYPE = args.connect
|
||||||
COMMB_TS = []
|
if DATATYPE not in support_rawtypes:
|
||||||
LOCK.release()
|
print("Data type not supported, available ones are %s" % support_rawtypes)
|
||||||
|
|
||||||
acs = stream.get_aircraft()
|
else:
|
||||||
try:
|
print('Source must be "rtlsdr" or "net".')
|
||||||
screen.update_data(acs)
|
sys.exit(1)
|
||||||
screen.update()
|
|
||||||
time.sleep(0.02)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
raise
|
|
||||||
except:
|
|
||||||
continue
|
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
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_pipe_in, raw_pipe_out = multiprocessing.Pipe()
|
||||||
|
ac_pipe_in, ac_pipe_out = multiprocessing.Pipe()
|
||||||
|
exception_queue = multiprocessing.Queue()
|
||||||
|
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, exception_queue)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
decode = Decode(latlon=LATLON, dumpto=DUMPTO)
|
||||||
|
decode_process = multiprocessing.Process(
|
||||||
|
target=decode.run, args=(raw_pipe_out, ac_pipe_in, exception_queue)
|
||||||
|
)
|
||||||
|
|
||||||
|
screen = Screen(uncertainty=UNCERTAINTY)
|
||||||
|
screen_process = multiprocessing.Process(
|
||||||
|
target=screen.run, args=(ac_pipe_out, exception_queue)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def shutdown():
|
||||||
|
stop_flag.value = True
|
||||||
|
curses.endwin()
|
||||||
|
sys.stdout = sys.__stdout__
|
||||||
|
recv_process.terminate()
|
||||||
|
decode_process.terminate()
|
||||||
|
screen_process.terminate()
|
||||||
|
recv_process.join()
|
||||||
|
decode_process.join()
|
||||||
|
screen_process.join()
|
||||||
|
|
||||||
|
|
||||||
|
def closeall(signal, frame):
|
||||||
|
print("KeyboardInterrupt (ID: {}). Cleaning up...".format(signal))
|
||||||
|
shutdown()
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
finally:
|
|
||||||
curses.endwin()
|
signal.signal(signal.SIGINT, closeall)
|
||||||
|
|
||||||
|
recv_process.start()
|
||||||
|
decode_process.start()
|
||||||
|
screen_process.start()
|
||||||
|
|
||||||
|
|
||||||
|
while True:
|
||||||
|
if (
|
||||||
|
(not recv_process.is_alive())
|
||||||
|
or (not decode_process.is_alive())
|
||||||
|
or (not screen_process.is_alive())
|
||||||
|
):
|
||||||
|
shutdown()
|
||||||
|
while not exception_queue.empty():
|
||||||
|
trackback = exception_queue.get()
|
||||||
|
print(trackback)
|
||||||
|
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
time.sleep(0.01)
|
||||||
|
|||||||
@@ -1,46 +1,46 @@
|
|||||||
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
|
||||||
|
import traceback
|
||||||
|
|
||||||
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,38 @@ 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, exception_queue):
|
||||||
|
local_buffer = []
|
||||||
|
key_thread = threading.Thread(target=self.kye_handling)
|
||||||
|
key_thread.daemon = True
|
||||||
|
key_thread.start()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
# raise RuntimeError("test exception")
|
||||||
|
|
||||||
|
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 = []
|
||||||
|
|
||||||
|
self.update()
|
||||||
|
except curses.error:
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
tb = traceback.format_exc()
|
||||||
|
exception_queue.put(tb)
|
||||||
|
time.sleep(0.1)
|
||||||
|
raise e
|
||||||
|
|
||||||
|
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
|
|
||||||
72
setup.py
72
setup.py
@@ -4,9 +4,9 @@ See:
|
|||||||
https://packaging.python.org/en/latest/distributing.html
|
https://packaging.python.org/en/latest/distributing.html
|
||||||
https://github.com/pypa/sampleproject
|
https://github.com/pypa/sampleproject
|
||||||
|
|
||||||
Steps for deploying a new verison:
|
Steps for deploying a new version:
|
||||||
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/*
|
||||||
@@ -14,6 +14,14 @@ 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.c_common", ["pyModeS/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
|
||||||
@@ -21,73 +29,55 @@ from os import path
|
|||||||
here = path.abspath(path.dirname(__file__))
|
here = path.abspath(path.dirname(__file__))
|
||||||
|
|
||||||
# Get the long description from the README file
|
# Get the long description from the README file
|
||||||
with open(path.join(here, 'README.rst'), encoding='utf-8') as f:
|
with open(path.join(here, "README.rst"), encoding="utf-8") as f:
|
||||||
long_description = f.read()
|
long_description = f.read()
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='pyModeS',
|
name="pyModeS",
|
||||||
|
|
||||||
# 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.0',
|
version="2.5",
|
||||||
|
description="Python Mode-S and ADS-B Decoder",
|
||||||
description='Python ADS-B/Mode-S Decoder',
|
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
|
|
||||||
# The project's main homepage.
|
# The project's main homepage.
|
||||||
url='https://github.com/junzis/pyModes',
|
url="https://github.com/junzis/pyModeS",
|
||||||
|
|
||||||
# Author details
|
# Author details
|
||||||
author='Junzi Sun',
|
author="Junzi Sun",
|
||||||
author_email='j.sun-1@tudelft.nl',
|
author_email="j.sun-1@tudelft.nl",
|
||||||
|
|
||||||
# Choose your license
|
# Choose your license
|
||||||
license='GNU GPL v3',
|
license="GNU GPL v3",
|
||||||
|
|
||||||
# See https://pypi.python.org/pypi?%3Aaction=list_classifiers
|
# See https://pypi.python.org/pypi?%3Aaction=list_classifiers
|
||||||
classifiers=[
|
classifiers=[
|
||||||
# How mature is this project? Common values are
|
# How mature is this project? Common values are
|
||||||
# 3 - Alpha
|
# 3 - Alpha
|
||||||
# 4 - Beta
|
# 4 - Beta
|
||||||
# 5 - Production/Stable
|
# 5 - Production/Stable
|
||||||
'Development Status :: 3 - Alpha',
|
"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 :: MIT License',
|
"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"]),
|
||||||
|
|
||||||
# Alternatively, if you want to distribute just a my_module.py, uncomment
|
# Alternatively, if you want to distribute just a my_module.py, uncomment
|
||||||
# this:
|
# this:
|
||||||
# py_modules=["my_module"],
|
# py_modules=["my_module"],
|
||||||
|
|
||||||
# List run-time dependencies here. These will be installed by pip when
|
# List run-time dependencies here. These will be installed by pip when
|
||||||
# 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", "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:
|
||||||
@@ -96,20 +86,17 @@ setup(
|
|||||||
# 'dev': ['check-manifest'],
|
# 'dev': ['check-manifest'],
|
||||||
# 'test': ['coverage'],
|
# 'test': ['coverage'],
|
||||||
# },
|
# },
|
||||||
|
|
||||||
# If there are data files included in your packages that need to be
|
# If there are data files included in your packages that need to be
|
||||||
# installed, specify them here. If using Python 2.6 or less, then these
|
# installed, specify them here. If using Python 2.6 or less, then these
|
||||||
# have to be included in MANIFEST.in as well.
|
# have to be included in MANIFEST.in as well.
|
||||||
# package_data={
|
# package_data={
|
||||||
# 'sample': ['package_data.dat'],
|
# 'sample': ['package_data.dat'],
|
||||||
# },
|
# },
|
||||||
|
|
||||||
# Although 'package_data' is the preferred approach, in some case you may
|
# Although 'package_data' is the preferred approach, in some case you may
|
||||||
# need to place data files outside of your packages. See:
|
# need to place data files outside of your packages. See:
|
||||||
# http://docs.python.org/3.4/distutils/setupscript.html#installing-additional-files # noqa
|
# http://docs.python.org/3.4/distutils/setupscript.html#installing-additional-files # noqa
|
||||||
# In this case, 'data_file' will be installed into '<sys.prefix>/my_data'
|
# In this case, 'data_file' will be installed into '<sys.prefix>/my_data'
|
||||||
# data_files=[('my_data', ['data/data_file'])],
|
# data_files=[('my_data', ['data/data_file'])],
|
||||||
|
|
||||||
# To provide executable scripts, use entry points in preference to the
|
# To provide executable scripts, use entry points in preference to the
|
||||||
# "scripts" keyword. Entry points provide cross-platform support and allow
|
# "scripts" keyword. Entry points provide cross-platform support and allow
|
||||||
# pip to create the appropriate form of executable for the target platform.
|
# pip to create the appropriate form of executable for the target platform.
|
||||||
@@ -118,6 +105,5 @@ setup(
|
|||||||
# 'sample=sample:main',
|
# 'sample=sample:main',
|
||||||
# ],
|
# ],
|
||||||
# },
|
# },
|
||||||
|
scripts=["pyModeS/streamer/modeslive"],
|
||||||
scripts=['pyModeS/streamer/modeslive'],
|
|
||||||
)
|
)
|
||||||
|
|||||||
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)
|
|
||||||
|
|||||||
@@ -1,32 +1,49 @@
|
|||||||
from __future__ import print_function
|
|
||||||
from pyModeS import commb, common, bds
|
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)]
|
||||||
|
|
||||||
elif BDS == "BDS40":
|
elif BDS == "BDS40":
|
||||||
info = (commb.alt40mcp(m), commb.alt40fms(m), commb.p40baro(m))
|
info = (commb.selalt40mcp(m), commb.selalt40fms(m), commb.p40baro(m))
|
||||||
|
|
||||||
elif BDS == "BDS44":
|
elif BDS == "BDS44":
|
||||||
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 +56,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 +71,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
|
||||||
|
|||||||
58
tests/test_c_common.py
Normal file
58
tests/test_c_common.py
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
try:
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
except:
|
||||||
|
pass
|
||||||
@@ -1,34 +1,36 @@
|
|||||||
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():
|
||||||
assert bds.bds40.alt40mcp("A000029C85E42F313000007047D3") == 3008
|
assert bds.bds40.selalt40mcp("A000029C85E42F313000007047D3") == 3008
|
||||||
assert bds.bds40.alt40fms("A000029C85E42F313000007047D3") == 3008
|
assert bds.bds40.selalt40fms("A000029C85E42F313000007047D3") == 3008
|
||||||
assert bds.bds40.p40baro("A000029C85E42F313000007047D3") == 1020.0
|
assert bds.bds40.p40baro("A000029C85E42F313000007047D3") == 1020.0
|
||||||
|
|
||||||
assert commb.alt40mcp("A000029C85E42F313000007047D3") == 3008
|
assert commb.selalt40mcp("A000029C85E42F313000007047D3") == 3008
|
||||||
assert commb.alt40fms("A000029C85E42F313000007047D3") == 3008
|
assert commb.selalt40fms("A000029C85E42F313000007047D3") == 3008
|
||||||
assert commb.p40baro("A000029C85E42F313000007047D3") == 1020.0
|
assert commb.p40baro("A000029C85E42F313000007047D3") == 1020.0
|
||||||
|
|
||||||
|
|
||||||
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,42 +1,62 @@
|
|||||||
from pyModeS import common
|
from pyModeS import common
|
||||||
|
|
||||||
|
|
||||||
def test_hex2bin():
|
def test_conversions():
|
||||||
assert common.hex2bin('6E406B') == "011011100100000001101011"
|
assert common.hex2bin("6E406B") == "011011100100000001101011"
|
||||||
|
|
||||||
|
|
||||||
def test_crc_decode():
|
def test_crc_decode():
|
||||||
checksum = common.crc("8D406B902015A678D4D220AA4BDA")
|
assert common.crc_legacy("8D406B902015A678D4D220AA4BDA") == 0
|
||||||
assert checksum == "000000000000000000000000"
|
|
||||||
|
assert common.crc("8D406B902015A678D4D220AA4BDA") == 0
|
||||||
|
assert common.crc("8d8960ed58bf053cf11bc5932b7d") == 0
|
||||||
|
assert common.crc("8d45cab390c39509496ca9a32912") == 0
|
||||||
|
assert common.crc("8d49d3d4e1089d00000000744c3b") == 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():
|
def test_crc_encode():
|
||||||
parity = common.crc("8D406B902015A678D4D220AA4BDA", encode=True)
|
parity = common.crc("8D406B902015A678D4D220AA4BDA", encode=True)
|
||||||
assert common.hex2bin("AA4BDA") == parity
|
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
|
||||||
|
|||||||
23
tests/test_encoder.py
Normal file
23
tests/test_encoder.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
from pyModeS import encoder
|
||||||
|
|
||||||
|
|
||||||
|
def test_identification():
|
||||||
|
msg = encoder.encode_adsb(
|
||||||
|
icao="406B90", typecode=4, capability=5, category=0, callsign="EZY85MH"
|
||||||
|
)
|
||||||
|
assert msg == "8D406B902015A678D4D220AA4BDA"
|
||||||
|
|
||||||
|
|
||||||
|
def test_speed():
|
||||||
|
msg = encoder.encode_adsb(
|
||||||
|
icao="485020",
|
||||||
|
typecode=19,
|
||||||
|
capability=5,
|
||||||
|
speed_type="gs",
|
||||||
|
speed=159,
|
||||||
|
angle=182.88,
|
||||||
|
vertical_rate=-832,
|
||||||
|
vertical_rate_source="gnss",
|
||||||
|
gnss_baro_alt_diff=550,
|
||||||
|
)
|
||||||
|
assert msg == "8D485020994409940838175B284F"
|
||||||
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