Compare commits
147 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
72fe72c16c | ||
|
|
31df5c2042 | ||
|
|
10e9b234ad | ||
|
|
546d69c129 | ||
|
|
c4c285266c | ||
|
|
ebc11e5e84 | ||
|
|
f5c2b36209 | ||
|
|
759380b5a9 | ||
|
|
3f6389a67d | ||
|
|
c252647e77 | ||
|
|
1795d2f3c0 | ||
|
|
fb4cef7085 | ||
|
|
b24c1101b6 | ||
|
|
8a43d711aa | ||
|
|
495f320988 | ||
|
|
44a9c8d2aa | ||
|
|
ecfc4037a1 | ||
|
|
61f2191feb | ||
|
|
9e1cc2c5a2 | ||
|
|
500c3ca9bd | ||
|
|
78f1ca77c7 | ||
|
|
576713f13b | ||
|
|
3502fedf02 | ||
|
|
de9ec43912 | ||
|
|
bfdb8221a0 | ||
|
|
32e6ee3904 | ||
|
|
d82dfd5537 | ||
|
|
16c83d1505 | ||
|
|
e5d5633535 | ||
|
|
8adacd8e91 | ||
|
|
fd15b13c17 | ||
|
|
405d8ed108 | ||
|
|
0085d03d4a | ||
|
|
6f139d4ae9 | ||
|
|
e5ca76ac0d | ||
|
|
ef9d2cfd16 | ||
|
|
ea1ccc0c70 | ||
|
|
648f4660b7 | ||
|
|
0df6a664a3 | ||
|
|
44b277f0ad | ||
|
|
715d0a3c66 | ||
|
|
6db5ea8023 | ||
|
|
7685f1590f | ||
|
|
5fa090b95f | ||
|
|
6c5ae2141b | ||
|
|
24f3658673 | ||
|
|
205725872a | ||
|
|
01a573a1af | ||
|
|
c0476f5e16 | ||
|
|
140b68afbc | ||
|
|
70b3af2c8b | ||
|
|
0ef64be934 | ||
|
|
bbe6e50fb2 | ||
|
|
a3e44b5626 | ||
|
|
457a948879 | ||
|
|
54b2038a41 | ||
|
|
82b912bc05 | ||
|
|
6d5869a9e0 | ||
|
|
36840e0225 | ||
|
|
972ffe264e | ||
|
|
d4ca81e0ca | ||
|
|
9fa475ab9a | ||
|
|
c91bd4bb03 | ||
|
|
4f0946c4da | ||
|
|
46a99852d6 | ||
|
|
2389c12b98 | ||
|
|
6128c5a18d | ||
|
|
72c1a9f645 | ||
|
|
f221c67295 | ||
|
|
14a537030d | ||
|
|
b7afd841ff | ||
|
|
11b85b6959 | ||
|
|
8a54f927f6 | ||
|
|
9bfc116516 | ||
|
|
be38acabfd | ||
|
|
0b6d5576d3 | ||
|
|
dfdddb77f2 | ||
|
|
a872cd253e | ||
|
|
edbfdc68de | ||
|
|
f246c88dd6 | ||
|
|
e821b8fa77 | ||
|
|
1b3dcef659 | ||
|
|
7653b2459d | ||
|
|
7c52db318d | ||
|
|
362b92de7a | ||
|
|
ef8f4b3b7f | ||
|
|
773efff9bc | ||
|
|
05e34e7516 | ||
|
|
161ea31ee1 | ||
|
|
12506e7c7f | ||
|
|
fcd98d6bcc | ||
|
|
ef40acdbfd | ||
|
|
8b48fabf5a | ||
|
|
d91ad261cd | ||
|
|
4911e69171 | ||
|
|
5697e5b88e | ||
|
|
3073187d24 | ||
|
|
441cd27761 | ||
|
|
c4406ba276 | ||
|
|
de6238f5e9 | ||
|
|
711fd889e6 | ||
|
|
e692d30d66 | ||
|
|
d6a04865dc | ||
|
|
fd8bb8386f | ||
|
|
6baa218596 | ||
|
|
8a9045e730 | ||
|
|
d13e1bd12e | ||
|
|
2b1f2a5878 | ||
|
|
3538645e22 | ||
|
|
a9887d6238 | ||
|
|
7c8fd74db7 | ||
|
|
35b0d63fa9 | ||
|
|
2ae7bf4c19 | ||
|
|
e8449154ca | ||
|
|
fbe5b63286 | ||
|
|
45c32cd7aa |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,6 +3,7 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
.pytest_cache/
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
364
README.rst
364
README.rst
@@ -1,50 +1,115 @@
|
||||
The Python Mode-S Decoder
|
||||
=========================
|
||||
The Python ADS-B/Mode-S Decoder
|
||||
===============================
|
||||
|
||||
Python library for Mode-S message decoding. Support Downlink Formats (DF) are:
|
||||
If you find this project useful for your research, please cite our work (bibtex format):
|
||||
|
||||
- Automatic Dependent Surveillance - Broadcast (ADS-B) (DF17)
|
||||
::
|
||||
|
||||
- aircraft information that contains: ICAO address, position, altitude, velocity (ground speed), callsign, etc.
|
||||
|
||||
- Mode-S Elementary Surveillance (ELS) (DF4 and DF5).
|
||||
|
||||
- DF4: Altitude
|
||||
- DF5: Squawk code
|
||||
|
||||
- Mode-S Enhanced Surveillance (EHS) (DF20 and DF21). Additional information in response to SSR interrogation, such as: true airspeed, indicated airspeed, mach number, wind, temperature, etc.
|
||||
|
||||
- DF20: Altitude
|
||||
- DF21: Squawk code
|
||||
- BDS 2,0 Aircraft identification
|
||||
- BDS 2,1 Aircraft and airline registration markings
|
||||
- BDS 4,0 Selected vertical intention
|
||||
- BDS 4,4 Meteorological routine air report
|
||||
- BDS 5,0 Track and turn report
|
||||
- BDS 5,3 Air-referenced state vector
|
||||
- BDS 6,0 Heading and speed report
|
||||
|
||||
Detailed manual on Mode-S decoding is published by the author, at:
|
||||
http://adsb-decode-guide.readthedocs.io
|
||||
@article{sun2019pymodes,
|
||||
author={J. {Sun} and H. {V\^u} and J. {Ellerbroek} and J. M. {Hoekstra}},
|
||||
journal={IEEE Transactions on Intelligent Transportation Systems},
|
||||
title={pyModeS: Decoding Mode-S Surveillance Data for Open Air Transportation Research},
|
||||
year={2019},
|
||||
doi={10.1109/TITS.2019.2914770},
|
||||
ISSN={1524-9050},
|
||||
}
|
||||
|
||||
|
||||
Source code
|
||||
|
||||
Introduction
|
||||
---------------------
|
||||
PyModeS is a Python library designed to decode Mode-S (including ADS-B) message.
|
||||
Message with following Downlink Formats (DF) are supported:
|
||||
|
||||
|
||||
**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 replies**
|
||||
|
||||
- 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
|
||||
|
||||
|
||||
**DF4 / DF20: Altitude code**
|
||||
|
||||
**DF5 / DF21: Identity code (squawk code)**
|
||||
|
||||
|
||||
Resources
|
||||
-----------
|
||||
Checkout and contribute to this open source project at:
|
||||
Checkout and contribute to this open-source project at:
|
||||
https://github.com/junzis/pyModeS
|
||||
|
||||
API documentation at:
|
||||
Detailed manual on Mode-S decoding is published at:
|
||||
https://mode-s.org/decode.
|
||||
|
||||
API documentation of pyModeS is at:
|
||||
http://pymodes.readthedocs.io
|
||||
|
||||
|
||||
|
||||
Install
|
||||
-------
|
||||
|
||||
The easiest installation is to use pip:
|
||||
To install latest version from the GitHub:
|
||||
|
||||
::
|
||||
|
||||
pip install pyModeS
|
||||
pip install git+https://github.com/junzis/pyModeS
|
||||
|
||||
|
||||
To install the stable version (2.0) from pip:
|
||||
|
||||
::
|
||||
|
||||
pip install pyModeS
|
||||
|
||||
|
||||
|
||||
Live view traffic (modeslive)
|
||||
----------------------------------------------------
|
||||
Supports **Mode-S Beast** and **AVR** raw stream
|
||||
|
||||
::
|
||||
|
||||
modeslive --server [server_address] --port [tcp_port] --rawtype [beast,avr,skysense] --latlon [lat] [lon] --dumpto [folder]
|
||||
|
||||
Arguments:
|
||||
-h, --help show this help message and exit
|
||||
--server SERVER server address or IP
|
||||
--port PORT raw data port
|
||||
--rawtype RAWTYPE beast, avr or skysense
|
||||
--latlon LAT LON receiver position
|
||||
--show-uncertainty display uncertaint values, default off
|
||||
--dumpto folder to dump decoded output
|
||||
|
||||
|
||||
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:
|
||||
|
||||
::
|
||||
|
||||
$ modesmixer2 --inSeriel port[:speed[:flow_control]] --outServer beast:[tcp_port]
|
||||
|
||||
Example screenshot:
|
||||
|
||||
.. image:: https://github.com/junzis/pyModeS/raw/master/doc/modeslive-screenshot.png
|
||||
:width: 700px
|
||||
|
||||
|
||||
Use the library
|
||||
@@ -52,46 +117,51 @@ Use the library
|
||||
|
||||
.. code:: python
|
||||
|
||||
import pyModeS as pms
|
||||
import pyModeS as pms
|
||||
|
||||
|
||||
Common functions:
|
||||
Common functions
|
||||
*****************
|
||||
|
||||
.. code:: python
|
||||
|
||||
pms.df(msg) # Downlink Format
|
||||
pms.crc(msg, encode=False) # Perform CRC or generate parity bit
|
||||
pms.df(msg) # Downlink Format
|
||||
pms.icao(msg) # Infer the ICAO address from the message
|
||||
pms.crc(msg, encode=False) # Perform CRC or generate parity bit
|
||||
|
||||
pms.hex2bin(str) # Convert hexadecimal string to binary string
|
||||
pms.bin2int(str) # Convert binary string to integer
|
||||
pms.hex2int(str) # Convert hexadecimal string to integer
|
||||
|
||||
pms.gray2int(str) # Convert grey code to interger
|
||||
pms.hex2bin(str) # Convert hexadecimal string to binary string
|
||||
pms.bin2int(str) # Convert binary string to integer
|
||||
pms.hex2int(str) # Convert hexadecimal string to integer
|
||||
pms.gray2int(str) # Convert grey code to interger
|
||||
|
||||
|
||||
Core functions for ADS-B decoding:
|
||||
**********************************
|
||||
Core functions for ADS-B decoding
|
||||
*********************************
|
||||
|
||||
.. code:: python
|
||||
|
||||
pms.adsb.icao(msg)
|
||||
pms.adsb.callsign(msg)
|
||||
pms.adsb.icao(msg)
|
||||
pms.adsb.typecode(msg)
|
||||
|
||||
pms.adsb.position(msg_even, msg_odd, t_even, t_odd, lat_ref=None, lon_ref=None)
|
||||
pms.adsb.airborne_position(msg_even, msg_odd, t_even, t_odd)
|
||||
pms.adsb.surface_position(msg_even, msg_odd, t_even, t_odd, lat_ref, lon_ref)
|
||||
# Typecode 1-4
|
||||
pms.adsb.callsign(msg)
|
||||
|
||||
pms.adsb.position_with_ref(msg, lat_ref, lon_ref)
|
||||
pms.adsb.airborne_position_with_ref(msg, lat_ref, lon_ref)
|
||||
pms.adsb.surface_position_with_ref(msg, lat_ref, lon_ref)
|
||||
# Typecode 5-8 (surface), 9-18 (airborne, barometric height), and 9-18 (airborne, GNSS height)
|
||||
pms.adsb.position(msg_even, msg_odd, t_even, t_odd, lat_ref=None, lon_ref=None)
|
||||
pms.adsb.airborne_position(msg_even, msg_odd, t_even, t_odd)
|
||||
pms.adsb.surface_position(msg_even, msg_odd, t_even, t_odd, lat_ref, lon_ref)
|
||||
|
||||
pms.adsb.altitude(msg)
|
||||
pms.adsb.position_with_ref(msg, lat_ref, lon_ref)
|
||||
pms.adsb.airborne_position_with_ref(msg, lat_ref, lon_ref)
|
||||
pms.adsb.surface_position_with_ref(msg, lat_ref, lon_ref)
|
||||
|
||||
pms.adsb.velocity(msg) # handles both surface & airborne messages
|
||||
pms.adsb.speed_heading(msg) # handles both surface & airborne messages
|
||||
pms.adsb.surface_velocity(msg)
|
||||
pms.adsb.airborne_velocity(msg)
|
||||
pms.adsb.altitude(msg)
|
||||
|
||||
# Typecode: 19
|
||||
pms.adsb.velocity(msg) # Handles both surface & airborne messages
|
||||
pms.adsb.speed_heading(msg) # Handles both surface & airborne messages
|
||||
pms.adsb.surface_velocity(msg)
|
||||
pms.adsb.airborne_velocity(msg)
|
||||
|
||||
|
||||
Note: When you have a fix position of the aircraft, it is convenient to
|
||||
@@ -100,71 +170,155 @@ use `position_with_ref()` method to decode with only one position message
|
||||
messages. But the reference position shall be with in 180NM (airborne)
|
||||
or 45NM (surface) of the true position.
|
||||
|
||||
Core functions for ELS decoding:
|
||||
********************************
|
||||
|
||||
Decode altitude replies in DF4 / DF20
|
||||
**************************************
|
||||
.. code:: python
|
||||
|
||||
pms.common.altcode(msg) # Downlink format must be 4 or 20
|
||||
|
||||
|
||||
Decode identity replies in DF5 / DF21
|
||||
**************************************
|
||||
.. code:: python
|
||||
|
||||
pms.common.idcode(msg) # Downlink format must be 5 or 21
|
||||
|
||||
|
||||
|
||||
Common Mode-S functions
|
||||
************************
|
||||
|
||||
.. code:: python
|
||||
|
||||
pms.els.icao(msg) # ICAO address
|
||||
pms.els.df4alt(msg) # Altitude from any DF4 message
|
||||
pms.ehs.df5id(msg) # Squawk code from any DF5 message
|
||||
pms.icao(msg) # Infer the ICAO address from the message
|
||||
pms.bds.infer(msg) # Infer the Modes-S BDS register
|
||||
|
||||
# Check if BDS is 5,0 or 6,0, give reference speed, track, altitude (from ADS-B)
|
||||
pms.bds.is50or60(msg, spd_ref, trk_ref, alt_ref)
|
||||
|
||||
# Check each BDS explicitly
|
||||
pms.bds.bds10.is10(msg)
|
||||
pms.bds.bds17.is17(msg)
|
||||
pms.bds.bds20.is20(msg)
|
||||
pms.bds.bds30.is30(msg)
|
||||
pms.bds.bds40.is40(msg)
|
||||
pms.bds.bds44.is44(msg)
|
||||
pms.bds.bds50.is50(msg)
|
||||
pms.bds.bds60.is60(msg)
|
||||
|
||||
|
||||
Core functions for EHS decoding:
|
||||
********************************
|
||||
|
||||
Mode-S Elementary Surveillance (ELS)
|
||||
*************************************
|
||||
|
||||
.. code:: python
|
||||
|
||||
pms.ehs.icao(msg) # ICAO address
|
||||
pms.ehs.df20alt(msg) # Altitude from any DF20 message
|
||||
pms.ehs.df21id(msg) # Squawk code from any DF21 message
|
||||
pms.commb.ovc10(msg) # Overlay capability, BDS 1,0
|
||||
pms.commb.cap17(msg) # GICB capability, BDS 1,7
|
||||
pms.commb.cs20(msg) # Callsign, BDS 2,0
|
||||
|
||||
pms.ehs.BDS(msg) # Comm-B Data Selector Version
|
||||
|
||||
# for BDS version 2,0
|
||||
pms.ehs.isBDS20(msg) # Check if message is BDS 2,0
|
||||
pms.ehs.callsign(msg) # Aircraft callsign
|
||||
Mode-S Enhanced Surveillance (EHS)
|
||||
***********************************
|
||||
|
||||
# for BDS version 4,0
|
||||
pms.ehs.isBDS40(msg) # Check if message is BDS 4,0
|
||||
pms.ehs.alt40mcp(msg) # MCP/FCU selected altitude (ft)
|
||||
pms.ehs.alt40fms(msg) # FMS selected altitude (ft)
|
||||
pms.ehs.p40baro(msg) # Barometric pressure (mb)
|
||||
.. code:: python
|
||||
|
||||
# for BDS version 4,4
|
||||
pms.ehs.isBDS44(msg, rev=False) # Check if message is BDS 4,4
|
||||
pms.ehs.wind44(msg, rev=False) # wind speed (kt) and heading (deg)
|
||||
pms.ehs.temp44(msg, rev=False) # temperature (C)
|
||||
pms.ehs.p44(msg, rev=False) # pressure (hPa)
|
||||
pms.ehs.hum44(msg, rev=False) # humidity (%)
|
||||
# BDS 4,0
|
||||
pms.commb.selalt40mcp(msg) # MCP/FCU selected altitude (ft)
|
||||
pms.commb.selalt40fms(msg) # FMS selected altitude (ft)
|
||||
pms.commb.p40baro(msg) # Barometric pressure (mb)
|
||||
|
||||
# for BDS version 5,0
|
||||
pms.ehs.isBDS50(msg) # Check if message is BDS 5,0
|
||||
pms.ehs.roll50(msg) # roll angle (deg)
|
||||
pms.ehs.trk50(msg) # track angle (deg)
|
||||
pms.ehs.gs50(msg) # ground speed (kt)
|
||||
pms.ehs.rtrk50(msg) # track angle rate (deg/sec)
|
||||
pms.ehs.tas50(msg) # true airspeed (kt)
|
||||
# BDS 5,0
|
||||
pms.commb.roll50(msg) # Roll angle (deg)
|
||||
pms.commb.trk50(msg) # True track angle (deg)
|
||||
pms.commb.gs50(msg) # Ground speed (kt)
|
||||
pms.commb.rtrk50(msg) # Track angle rate (deg/sec)
|
||||
pms.commb.tas50(msg) # True airspeed (kt)
|
||||
|
||||
# for BDS version 5,3
|
||||
pms.ehs.isBDS53(msg) # Check if message is BDS 5,3
|
||||
pms.ehs.hdg53(msg) # magnetic heading (deg)
|
||||
pms.ehs.ias53(msg) # indicated airspeed (kt)
|
||||
pms.ehs.mach53(msg) # MACH number
|
||||
pms.ehs.tas53(msg) # true airspeed (kt)
|
||||
pms.ehs.vr53(msg) # vertical rate (fpm)
|
||||
# BDS 6,0
|
||||
pms.commb.hdg60(msg) # Magnetic heading (deg)
|
||||
pms.commb.ias60(msg) # Indicated airspeed (kt)
|
||||
pms.commb.mach60(msg) # Mach number (-)
|
||||
pms.commb.vr60baro(msg) # Barometric altitude rate (ft/min)
|
||||
pms.commb.vr60ins(msg) # Inertial vertical speed (ft/min)
|
||||
|
||||
# for BDS version 6,0
|
||||
pms.ehs.isBDS60(msg) # Check if message is BDS 6,0
|
||||
pms.ehs.hdg60(msg) # heading (deg)
|
||||
pms.ehs.ias60(msg) # indicated airspeed (kt)
|
||||
pms.ehs.mach60(msg) # MACH number
|
||||
pms.ehs.vr60baro(msg) # barometric altitude rate (ft/min)
|
||||
pms.ehs.vr60ins(msg) # inertial vertical speed (ft/min)
|
||||
|
||||
Developement
|
||||
------------
|
||||
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
|
||||
|
||||
# BDS 4,5
|
||||
pms.commb.turb45(msg) # Turbulence level (0-3)
|
||||
pms.commb.ws45(msg) # Wind shear level (0-3)
|
||||
pms.commb.mb45(msg) # Microburst level (0-3)
|
||||
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)
|
||||
|
||||
|
||||
|
||||
Customize the streaming module
|
||||
******************************
|
||||
The TCP client module from pyModeS can be re-used to stream and process Mode-S
|
||||
data as your like. You need to re-implement the ``handle_messages()`` function from
|
||||
the ``BaseClient`` class to write your own logic to handle the messages.
|
||||
|
||||
Here is an example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
from pyModeS.extra.tcpclient import BaseClient
|
||||
|
||||
# define your custom class by extending the BaseClient
|
||||
# - implement your handle_messages() methods
|
||||
class ADSBClient(BaseClient):
|
||||
def __init__(self, host, port, rawtype):
|
||||
super(ModesClient, 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 '1' in pms.crc(msg): # 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=30334, rawtype='beast')
|
||||
client.run()
|
||||
|
||||
|
||||
Unit test
|
||||
---------
|
||||
To perform unit tests. First install ``tox`` through pip, Then, run the following commands:
|
||||
```
|
||||
$ tox
|
||||
```
|
||||
|
||||
.. code:: bash
|
||||
|
||||
$ tox
|
||||
|
||||
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.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = _build
|
||||
SOURCEDIR = source
|
||||
BUILDDIR = build
|
||||
|
||||
# Internal variables.
|
||||
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
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@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"
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf $(BUILDDIR)/*
|
||||
.PHONY: help Makefile
|
||||
|
||||
.PHONY: html
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
.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."
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
rm -f source/pyModeS*.rst source/modules.rst
|
||||
sphinx-apidoc -f -e -M -o source/ ../pyModeS
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
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:
|
||||
|
||||
BIN
doc/modeslive-screenshot.png
Normal file
BIN
doc/modeslive-screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 98 KiB |
@@ -1 +0,0 @@
|
||||
pyModeS==1.1.0
|
||||
188
doc/source/conf.py
Normal file
188
doc/source/conf.py
Normal file
@@ -0,0 +1,188 @@
|
||||
# -*- 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.todo',
|
||||
'sphinx.ext.coverage',
|
||||
'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 = ['']
|
||||
|
||||
# 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'
|
||||
|
||||
# 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
|
||||
30
doc/source/index.rst
Normal file
30
doc/source/index.rst
Normal file
@@ -0,0 +1,30 @@
|
||||
.. 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::
|
||||
:maxdepth: 3
|
||||
|
||||
pyModeS.decoder
|
||||
pyModeS.streamer
|
||||
pyModeS.extra
|
||||
|
||||
|
||||
----
|
||||
|
||||
.. include:: ../../README.rst
|
||||
|
||||
----
|
||||
|
||||
Indices and tables
|
||||
**********************
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
35
doc/source/make.bat
Normal file
35
doc/source/make.bat
Normal file
@@ -0,0 +1,35 @@
|
||||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=.
|
||||
set BUILDDIR=_build
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.http://sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
||||
|
||||
:end
|
||||
popd
|
||||
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.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::
|
||||
|
||||
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:
|
||||
7
doc/source/pyModeS.decoder.common.rst
Normal file
7
doc/source/pyModeS.decoder.common.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
pyModeS.decoder.common module
|
||||
=============================
|
||||
|
||||
.. automodule:: pyModeS.decoder.common
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
doc/source/pyModeS.decoder.ehs.rst
Normal file
7
doc/source/pyModeS.decoder.ehs.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
pyModeS.decoder.ehs module
|
||||
==========================
|
||||
|
||||
.. automodule:: pyModeS.decoder.ehs
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
doc/source/pyModeS.decoder.els.rst
Normal file
7
doc/source/pyModeS.decoder.els.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
pyModeS.decoder.els module
|
||||
==========================
|
||||
|
||||
.. automodule:: pyModeS.decoder.els
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
30
doc/source/pyModeS.decoder.rst
Normal file
30
doc/source/pyModeS.decoder.rst
Normal file
@@ -0,0 +1,30 @@
|
||||
pyModeS.decoder package
|
||||
=======================
|
||||
|
||||
.. automodule:: pyModeS.decoder
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Subpackages
|
||||
-----------
|
||||
|
||||
.. toctree::
|
||||
|
||||
pyModeS.decoder.bds
|
||||
|
||||
Submodules
|
||||
----------
|
||||
|
||||
.. toctree::
|
||||
|
||||
pyModeS.decoder.acas
|
||||
pyModeS.decoder.adsb
|
||||
pyModeS.decoder.allcall
|
||||
pyModeS.decoder.commb
|
||||
pyModeS.decoder.common
|
||||
pyModeS.decoder.ehs
|
||||
pyModeS.decoder.els
|
||||
pyModeS.decoder.surv
|
||||
pyModeS.decoder.uncertainty
|
||||
|
||||
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.extra.aero.rst
Normal file
7
doc/source/pyModeS.extra.aero.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
pyModeS.extra.aero module
|
||||
=========================
|
||||
|
||||
.. automodule:: pyModeS.extra.aero
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
16
doc/source/pyModeS.extra.rst
Normal file
16
doc/source/pyModeS.extra.rst
Normal file
@@ -0,0 +1,16 @@
|
||||
pyModeS.extra package
|
||||
=====================
|
||||
|
||||
.. automodule:: pyModeS.extra
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Submodules
|
||||
----------
|
||||
|
||||
.. toctree::
|
||||
|
||||
pyModeS.extra.aero
|
||||
pyModeS.extra.tcpclient
|
||||
|
||||
7
doc/source/pyModeS.extra.tcpclient.rst
Normal file
7
doc/source/pyModeS.extra.tcpclient.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
pyModeS.extra.tcpclient module
|
||||
==============================
|
||||
|
||||
.. automodule:: pyModeS.extra.tcpclient
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
17
doc/source/pyModeS.rst
Normal file
17
doc/source/pyModeS.rst
Normal file
@@ -0,0 +1,17 @@
|
||||
pyModeS package
|
||||
===============
|
||||
|
||||
.. automodule:: pyModeS
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Subpackages
|
||||
-----------
|
||||
|
||||
.. toctree::
|
||||
|
||||
pyModeS.decoder
|
||||
pyModeS.extra
|
||||
pyModeS.streamer
|
||||
|
||||
16
doc/source/pyModeS.streamer.rst
Normal file
16
doc/source/pyModeS.streamer.rst
Normal file
@@ -0,0 +1,16 @@
|
||||
pyModeS.streamer package
|
||||
========================
|
||||
|
||||
.. automodule:: pyModeS.streamer
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Submodules
|
||||
----------
|
||||
|
||||
.. toctree::
|
||||
|
||||
pyModeS.streamer.screen
|
||||
pyModeS.streamer.stream
|
||||
|
||||
7
doc/source/pyModeS.streamer.screen.rst
Normal file
7
doc/source/pyModeS.streamer.screen.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
pyModeS.streamer.screen module
|
||||
==============================
|
||||
|
||||
.. automodule:: pyModeS.streamer.screen
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
doc/source/pyModeS.streamer.stream.rst
Normal file
7
doc/source/pyModeS.streamer.stream.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
pyModeS.streamer.stream module
|
||||
==============================
|
||||
|
||||
.. automodule:: pyModeS.streamer.stream
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,6 +1,15 @@
|
||||
from __future__ import absolute_import, print_function, division
|
||||
|
||||
from .util import *
|
||||
from . import adsb
|
||||
from . import ehs
|
||||
from . import els
|
||||
from .decoder.common import *
|
||||
from .decoder import adsb
|
||||
from .decoder import commb
|
||||
from .decoder import common
|
||||
from .decoder import bds
|
||||
from .extra import aero
|
||||
from .extra import tcpclient
|
||||
|
||||
# from .decoder import els # depricated
|
||||
# from .decoder import ehs # depricated
|
||||
|
||||
import os
|
||||
dirpath = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
691
pyModeS/adsb.py
691
pyModeS/adsb.py
@@ -1,691 +0,0 @@
|
||||
# Copyright (C) 2015 Junzi Sun (TU Delft)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
A python package for decoding ABS-D messages.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
import math
|
||||
from . import util
|
||||
|
||||
|
||||
def df(msg):
|
||||
"""Get the downlink format (DF) number
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
int: DF number
|
||||
"""
|
||||
return util.df(msg)
|
||||
|
||||
|
||||
def icao(msg):
|
||||
"""Get the ICAO 24 bits address, bytes 3 to 8.
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
String: ICAO address in 6 bytes hexadecimal string
|
||||
"""
|
||||
return msg[2:8]
|
||||
|
||||
|
||||
def data(msg):
|
||||
"""Return the data frame in the message, bytes 9 to 22"""
|
||||
return msg[8:22]
|
||||
|
||||
|
||||
def typecode(msg):
|
||||
"""Type code of ADS-B message
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
int: type code number
|
||||
"""
|
||||
msgbin = util.hex2bin(msg)
|
||||
return util.bin2int(msgbin[32:37])
|
||||
|
||||
|
||||
# ---------------------------------------------
|
||||
# Aircraft Identification
|
||||
# ---------------------------------------------
|
||||
def category(msg):
|
||||
"""Aircraft category number
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
int: category number
|
||||
"""
|
||||
|
||||
if typecode(msg) < 1 or typecode(msg) > 4:
|
||||
raise RuntimeError("%s: Not a identification message" % msg)
|
||||
msgbin = util.hex2bin(msg)
|
||||
return util.bin2int(msgbin[5:8])
|
||||
|
||||
|
||||
def callsign(msg):
|
||||
"""Aircraft callsign
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
string: callsign
|
||||
"""
|
||||
|
||||
if typecode(msg) < 1 or typecode(msg) > 4:
|
||||
raise RuntimeError("%s: Not a identification message" % msg)
|
||||
|
||||
chars = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ#####_###############0123456789######'
|
||||
msgbin = util.hex2bin(msg)
|
||||
csbin = msgbin[40:96]
|
||||
|
||||
cs = ''
|
||||
cs += chars[util.bin2int(csbin[0:6])]
|
||||
cs += chars[util.bin2int(csbin[6:12])]
|
||||
cs += chars[util.bin2int(csbin[12:18])]
|
||||
cs += chars[util.bin2int(csbin[18:24])]
|
||||
cs += chars[util.bin2int(csbin[24:30])]
|
||||
cs += chars[util.bin2int(csbin[30:36])]
|
||||
cs += chars[util.bin2int(csbin[36:42])]
|
||||
cs += chars[util.bin2int(csbin[42:48])]
|
||||
|
||||
# clean string, remove spaces and marks, if any.
|
||||
# cs = cs.replace('_', '')
|
||||
cs = cs.replace('#', '')
|
||||
return cs
|
||||
|
||||
|
||||
# ---------------------------------------------
|
||||
# Positions
|
||||
# ---------------------------------------------
|
||||
|
||||
def oe_flag(msg):
|
||||
"""Check the odd/even flag. Bit 54, 0 for even, 1 for odd.
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
int: 0 or 1, for even or odd frame
|
||||
"""
|
||||
if typecode(msg) < 5 or typecode(msg) > 18:
|
||||
raise RuntimeError("%s: Not a position message" % msg)
|
||||
|
||||
msgbin = util.hex2bin(msg)
|
||||
return int(msgbin[53])
|
||||
|
||||
|
||||
def cprlat(msg):
|
||||
"""CPR encoded latitude
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
int: encoded latitude
|
||||
"""
|
||||
if typecode(msg) < 5 or typecode(msg) > 18:
|
||||
raise RuntimeError("%s: Not a position message" % msg)
|
||||
|
||||
msgbin = util.hex2bin(msg)
|
||||
return util.bin2int(msgbin[54:71])
|
||||
|
||||
|
||||
def cprlon(msg):
|
||||
"""CPR encoded longitude
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
int: encoded longitude
|
||||
"""
|
||||
if typecode(msg) < 5 or typecode(msg) > 18:
|
||||
raise RuntimeError("%s: Not a position message" % msg)
|
||||
|
||||
msgbin = util.hex2bin(msg)
|
||||
return util.bin2int(msgbin[71:88])
|
||||
|
||||
|
||||
def position(msg0, msg1, t0, t1, lat_ref=None, lon_ref=None):
|
||||
"""Decode position from a pair of even and odd position message
|
||||
(works with both airborne and surface position messages)
|
||||
|
||||
Args:
|
||||
msg0 (string): even message (28 bytes hexadecimal string)
|
||||
msg1 (string): odd message (28 bytes hexadecimal string)
|
||||
t0 (int): timestamps for the even message
|
||||
t1 (int): timestamps for the odd message
|
||||
|
||||
Returns:
|
||||
(float, float): (latitude, longitude) of the aircraft
|
||||
"""
|
||||
if (5 <= typecode(msg0) <= 8 and 5 <= typecode(msg1) <= 8):
|
||||
if (not lat_ref) or (not lon_ref):
|
||||
raise RuntimeError("Surface position encountered, a reference \
|
||||
position lat/lon required. Location of \
|
||||
receiver can be used.")
|
||||
else:
|
||||
return surface_position(msg0, msg1, t0, t1, lat_ref, lon_ref)
|
||||
|
||||
elif (9 <= typecode(msg0) <= 18 and 9 <= typecode(msg1) <= 18):
|
||||
return airborne_position(msg0, msg1, t0, t1)
|
||||
|
||||
else:
|
||||
raise RuntimeError("incorrect or inconsistant message types")
|
||||
|
||||
|
||||
def airborne_position(msg0, msg1, t0, t1):
|
||||
"""Decode airborn position from a pair of even and odd position message
|
||||
|
||||
Args:
|
||||
msg0 (string): even message (28 bytes hexadecimal string)
|
||||
msg1 (string): odd message (28 bytes hexadecimal string)
|
||||
t0 (int): timestamps for the even message
|
||||
t1 (int): timestamps for the odd message
|
||||
|
||||
Returns:
|
||||
(float, float): (latitude, longitude) of the aircraft
|
||||
"""
|
||||
|
||||
msgbin0 = util.hex2bin(msg0)
|
||||
msgbin1 = util.hex2bin(msg1)
|
||||
|
||||
# 131072 is 2^17, since CPR lat and lon are 17 bits each.
|
||||
cprlat_even = util.bin2int(msgbin0[54:71]) / 131072.0
|
||||
cprlon_even = util.bin2int(msgbin0[71:88]) / 131072.0
|
||||
cprlat_odd = util.bin2int(msgbin1[54:71]) / 131072.0
|
||||
cprlon_odd = util.bin2int(msgbin1[71:88]) / 131072.0
|
||||
|
||||
air_d_lat_even = 360.0 / 60
|
||||
air_d_lat_odd = 360.0 / 59
|
||||
|
||||
# compute latitude index 'j'
|
||||
j = util.floor(59 * cprlat_even - 60 * cprlat_odd + 0.5)
|
||||
|
||||
lat_even = float(air_d_lat_even * (j % 60 + cprlat_even))
|
||||
lat_odd = float(air_d_lat_odd * (j % 59 + cprlat_odd))
|
||||
|
||||
if lat_even >= 270:
|
||||
lat_even = lat_even - 360
|
||||
|
||||
if lat_odd >= 270:
|
||||
lat_odd = lat_odd - 360
|
||||
|
||||
# check if both are in the same latidude zone, exit if not
|
||||
if _cprNL(lat_even) != _cprNL(lat_odd):
|
||||
return None
|
||||
|
||||
# compute ni, longitude index m, and longitude
|
||||
if (t0 > t1):
|
||||
lat = lat_even
|
||||
nl = _cprNL(lat)
|
||||
ni = max(_cprNL(lat)- 0, 1)
|
||||
m = util.floor(cprlon_even * (nl-1) - cprlon_odd * nl + 0.5)
|
||||
lon = (360.0 / ni) * (m % ni + cprlon_even)
|
||||
else:
|
||||
lat = lat_odd
|
||||
nl = _cprNL(lat)
|
||||
ni = max(_cprNL(lat) - 1, 1)
|
||||
m = util.floor(cprlon_even * (nl-1) - cprlon_odd * nl + 0.5)
|
||||
lon = (360.0 / ni) * (m % ni + cprlon_odd)
|
||||
|
||||
if lon > 180:
|
||||
lon = lon - 360
|
||||
|
||||
return round(lat, 5), round(lon, 5)
|
||||
|
||||
|
||||
def position_with_ref(msg, lat_ref, lon_ref):
|
||||
"""Decode position with only one message,
|
||||
knowing reference nearby location, such as previously
|
||||
calculated location, ground station, or airport location, etc.
|
||||
Works with both airborne and surface position messages.
|
||||
The reference position shall be with in 180NM (airborne) or 45NM (surface)
|
||||
of the true position.
|
||||
|
||||
Args:
|
||||
msg0 (string): even message (28 bytes hexadecimal string)
|
||||
msg1 (string): odd message (28 bytes hexadecimal string)
|
||||
t0 (int): timestamps for the even message
|
||||
t1 (int): timestamps for the odd message
|
||||
|
||||
Returns:
|
||||
(float, float): (latitude, longitude) of the aircraft
|
||||
"""
|
||||
if 5 <= typecode(msg) <= 8:
|
||||
return surface_position_with_ref(msg, lat_ref, lon_ref)
|
||||
|
||||
elif 9 <= typecode(msg) <= 18:
|
||||
return airborne_position_with_ref(msg, lat_ref, lon_ref)
|
||||
|
||||
else:
|
||||
raise RuntimeError("incorrect or inconsistant message types")
|
||||
|
||||
|
||||
def airborne_position_with_ref(msg, lat_ref, lon_ref):
|
||||
"""Decode airborne position with only one message,
|
||||
knowing reference nearby location, such as previously calculated location,
|
||||
ground station, or airport location, etc. The reference position shall
|
||||
be with in 180NM of the true position.
|
||||
|
||||
Args:
|
||||
msg (string): even message (28 bytes hexadecimal string)
|
||||
lat_ref: previous known latitude
|
||||
lon_ref: previous known longitude
|
||||
|
||||
Returns:
|
||||
(float, float): (latitude, longitude) of the aircraft
|
||||
"""
|
||||
|
||||
i = oe_flag(msg)
|
||||
d_lat = 360.0/59 if i else 360.0/60
|
||||
|
||||
msgbin = util.hex2bin(msg)
|
||||
cprlat = util.bin2int(msgbin[54:71]) / 131072.0
|
||||
cprlon = util.bin2int(msgbin[71:88]) / 131072.0
|
||||
|
||||
j = util.floor(lat_ref / d_lat) \
|
||||
+ util.floor(0.5 + ((lat_ref % d_lat) / d_lat) - cprlat)
|
||||
|
||||
lat = d_lat * (j + cprlat)
|
||||
|
||||
ni = _cprNL(lat) - i
|
||||
|
||||
if ni > 0:
|
||||
d_lon = 360.0 / ni
|
||||
else:
|
||||
d_lon = 360.0
|
||||
|
||||
m = util.floor(lon_ref / d_lon) \
|
||||
+ util.floor(0.5 + ((lon_ref % d_lon) / d_lon) - cprlon)
|
||||
|
||||
lon = d_lon * (m + cprlon)
|
||||
|
||||
return round(lat, 5), round(lon, 5)
|
||||
|
||||
|
||||
def surface_position(msg0, msg1, t0, t1, lat_ref, lon_ref):
|
||||
"""Decode surface position from a pair of even and odd position message,
|
||||
the lat/lon of receiver must be provided to yield the correct solution.
|
||||
|
||||
Args:
|
||||
msg0 (string): even message (28 bytes hexadecimal string)
|
||||
msg1 (string): odd message (28 bytes hexadecimal string)
|
||||
t0 (int): timestamps for the even message
|
||||
t1 (int): timestamps for the odd message
|
||||
lat_ref (float): latitude of the receiver
|
||||
lon_ref (float): longitude of the receiver
|
||||
|
||||
Returns:
|
||||
(float, float): (latitude, longitude) of the aircraft
|
||||
"""
|
||||
|
||||
msgbin0 = util.hex2bin(msg0)
|
||||
msgbin1 = util.hex2bin(msg1)
|
||||
|
||||
# 131072 is 2^17, since CPR lat and lon are 17 bits each.
|
||||
cprlat_even = util.bin2int(msgbin0[54:71]) / 131072.0
|
||||
cprlon_even = util.bin2int(msgbin0[71:88]) / 131072.0
|
||||
cprlat_odd = util.bin2int(msgbin1[54:71]) / 131072.0
|
||||
cprlon_odd = util.bin2int(msgbin1[71:88]) / 131072.0
|
||||
|
||||
air_d_lat_even = 90.0 / 60
|
||||
air_d_lat_odd = 90.0 / 59
|
||||
|
||||
# compute latitude index 'j'
|
||||
j = util.floor(59 * cprlat_even - 60 * cprlat_odd + 0.5)
|
||||
|
||||
# solution for north hemisphere
|
||||
lat_even_n = float(air_d_lat_even * (j % 60 + cprlat_even))
|
||||
lat_odd_n = float(air_d_lat_odd * (j % 59 + cprlat_odd))
|
||||
|
||||
# solution for north hemisphere
|
||||
lat_even_s = lat_even_n - 90.0
|
||||
lat_odd_s = lat_odd_n - 90.0
|
||||
|
||||
# chose which solution corrispondes to receiver location
|
||||
lat_even = lat_even_n if lat_ref > 0 else lat_even_s
|
||||
lat_odd = lat_odd_n if lat_ref > 0 else lat_odd_s
|
||||
|
||||
# check if both are in the same latidude zone, rare but possible
|
||||
if _cprNL(lat_even) != _cprNL(lat_odd):
|
||||
return None
|
||||
|
||||
# compute ni, longitude index m, and longitude
|
||||
if (t0 > t1):
|
||||
lat = lat_even
|
||||
nl = _cprNL(lat_even)
|
||||
ni = max(_cprNL(lat_even) - 0, 1)
|
||||
m = util.floor(cprlon_even * (nl-1) - cprlon_odd * nl + 0.5)
|
||||
lon = (90.0 / ni) * (m % ni + cprlon_even)
|
||||
else:
|
||||
lat = lat_odd
|
||||
nl = _cprNL(lat_odd)
|
||||
ni = max(_cprNL(lat_odd) - 1, 1)
|
||||
m = util.floor(cprlon_even * (nl-1) - cprlon_odd * nl + 0.5)
|
||||
lon = (90.0 / ni) * (m % ni + cprlon_odd)
|
||||
|
||||
# four possible longitude solutions
|
||||
lons = [lon, lon + 90.0, lon + 180.0, lon + 270.0]
|
||||
|
||||
# the closest solution to receiver is the correct one
|
||||
dls = [abs(lon_ref - l) for l in lons]
|
||||
imin = min(range(4), key=dls.__getitem__)
|
||||
lon = lons[imin]
|
||||
|
||||
return round(lat, 5), round(lon, 5)
|
||||
|
||||
|
||||
def surface_position_with_ref(msg, lat_ref, lon_ref):
|
||||
"""Decode surface position with only one message,
|
||||
knowing reference nearby location, such as previously calculated location,
|
||||
ground station, or airport location, etc. The reference position shall
|
||||
be with in 45NM of the true position.
|
||||
|
||||
Args:
|
||||
msg (string): even message (28 bytes hexadecimal string)
|
||||
lat_ref: previous known latitude
|
||||
lon_ref: previous known longitude
|
||||
|
||||
Returns:
|
||||
(float, float): (latitude, longitude) of the aircraft
|
||||
"""
|
||||
|
||||
i = oe_flag(msg)
|
||||
d_lat = 90.0/59 if i else 90.0/60
|
||||
|
||||
msgbin = util.hex2bin(msg)
|
||||
cprlat = util.bin2int(msgbin[54:71]) / 131072.0
|
||||
cprlon = util.bin2int(msgbin[71:88]) / 131072.0
|
||||
|
||||
j = util.floor(lat_ref / d_lat) \
|
||||
+ util.floor(0.5 + ((lat_ref % d_lat) / d_lat) - cprlat)
|
||||
|
||||
lat = d_lat * (j + cprlat)
|
||||
|
||||
ni = _cprNL(lat) - i
|
||||
|
||||
if ni > 0:
|
||||
d_lon = 90.0 / ni
|
||||
else:
|
||||
d_lon = 90.0
|
||||
|
||||
m = util.floor(lon_ref / d_lon) \
|
||||
+ util.floor(0.5 + ((lon_ref % d_lon) / d_lon) - cprlon)
|
||||
|
||||
lon = d_lon * (m + cprlon)
|
||||
|
||||
return round(lat, 5), round(lon, 5)
|
||||
|
||||
|
||||
def _cprNL(lat):
|
||||
"""NL() function in CPR decoding
|
||||
"""
|
||||
if lat == 0:
|
||||
return 59
|
||||
|
||||
if lat == 87 or lat == -87:
|
||||
return 2
|
||||
|
||||
if lat > 87 or lat < -87:
|
||||
return 1
|
||||
|
||||
nz = 15
|
||||
a = 1 - math.cos(math.pi / (2 * nz))
|
||||
b = math.cos(math.pi / 180.0 * abs(lat)) ** 2
|
||||
nl = 2 * math.pi / (math.acos(1 - a/b))
|
||||
NL = util.floor(nl)
|
||||
return NL
|
||||
|
||||
|
||||
def altitude(msg):
|
||||
"""Decode aircraft altitude
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
int: altitude in feet
|
||||
"""
|
||||
|
||||
if typecode(msg) < 5 or typecode(msg) > 18:
|
||||
raise RuntimeError("%s: Not a position message" % msg)
|
||||
|
||||
if typecode(msg) >=5 and typecode(msg) <=8:
|
||||
# surface position, altitude 0
|
||||
return 0
|
||||
|
||||
msgbin = util.hex2bin(msg)
|
||||
q = msgbin[47]
|
||||
if q:
|
||||
n = util.bin2int(msgbin[40:47]+msgbin[48:52])
|
||||
alt = n * 25 - 1000
|
||||
return alt
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def nic(msg):
|
||||
"""Calculate NIC, navigation integrity category
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
int: NIC number (from 0 to 11), -1 if not applicable
|
||||
"""
|
||||
if typecode(msg) < 9 or typecode(msg) > 18:
|
||||
raise RuntimeError("%s: Not a airborne position message, expecting 8<TC<19" % msg)
|
||||
|
||||
msgbin = util.hex2bin(msg)
|
||||
tc = typecode(msg)
|
||||
nic_sup_b = util.bin2int(msgbin[39])
|
||||
|
||||
if tc in [0, 18, 22]:
|
||||
nic = 0
|
||||
elif tc == 17:
|
||||
nic = 1
|
||||
elif tc == 16:
|
||||
if nic_sup_b:
|
||||
nic = 3
|
||||
else:
|
||||
nic = 2
|
||||
elif tc == 15:
|
||||
nic = 4
|
||||
elif tc == 14:
|
||||
nic = 5
|
||||
elif tc == 13:
|
||||
nic = 6
|
||||
elif tc == 12:
|
||||
nic = 7
|
||||
elif tc == 11:
|
||||
if nic_sup_b:
|
||||
nic = 9
|
||||
else:
|
||||
nic = 8
|
||||
elif tc in [10, 21]:
|
||||
nic = 10
|
||||
elif tc in [9, 20]:
|
||||
nic = 11
|
||||
else:
|
||||
nic = -1
|
||||
return nic
|
||||
|
||||
|
||||
# ---------------------------------------------
|
||||
# Velocity
|
||||
# ---------------------------------------------
|
||||
|
||||
def velocity(msg):
|
||||
"""Calculate the speed, heading, and vertical rate
|
||||
(handles both airborne or surface message)
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
(int, float, int, string): speed (kt), heading (degree),
|
||||
rate of climb/descend (ft/min), and speed type
|
||||
('GS' for ground speed, 'AS' for airspeed)
|
||||
"""
|
||||
|
||||
if 5 <= typecode(msg) <= 8:
|
||||
return surface_velocity(msg)
|
||||
|
||||
elif typecode(msg) == 19:
|
||||
return airborne_velocity(msg)
|
||||
|
||||
else:
|
||||
raise RuntimeError("incorrect or inconsistant message types, expecting 4<TC<9 or TC=19")
|
||||
|
||||
def speed_heading(msg):
|
||||
"""Get speed and heading only from the velocity message
|
||||
(handles both airborne or surface message)
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
(int, float): speed (kt), heading (degree)
|
||||
"""
|
||||
spd, hdg, rocd, tag = velocity(msg)
|
||||
return spd, hdg
|
||||
|
||||
|
||||
def airborne_velocity(msg):
|
||||
"""Calculate the speed, heading, and vertical rate
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
(int, float, int, string): speed (kt), heading (degree),
|
||||
rate of climb/descend (ft/min), and speed type
|
||||
('GS' for ground speed, 'AS' for airspeed)
|
||||
"""
|
||||
|
||||
if typecode(msg) != 19:
|
||||
raise RuntimeError("%s: Not a airborne velocity message, expecting TC=19" % msg)
|
||||
|
||||
msgbin = util.hex2bin(msg)
|
||||
|
||||
subtype = util.bin2int(msgbin[37:40])
|
||||
|
||||
if subtype in (1, 2):
|
||||
v_ew_sign = -1 if int(msgbin[45]) else 1
|
||||
v_ew = util.bin2int(msgbin[46:56]) - 1 # east-west velocity
|
||||
|
||||
v_ns_sign = -1 if int(msgbin[56]) else 1
|
||||
v_ns = util.bin2int(msgbin[57:67]) - 1 # north-south velocity
|
||||
|
||||
v_we = v_ew_sign * v_ew
|
||||
v_sn = v_ns_sign * v_ns
|
||||
|
||||
spd = math.sqrt(v_sn*v_sn + v_we*v_we) # unit in kts
|
||||
|
||||
hdg = math.atan2(v_we, v_sn)
|
||||
hdg = math.degrees(hdg) # convert to degrees
|
||||
hdg = hdg if hdg >= 0 else hdg + 360 # no negative val
|
||||
|
||||
tag = 'GS'
|
||||
|
||||
else:
|
||||
hdg = util.bin2int(msgbin[46:56]) / 1024.0 * 360.0
|
||||
spd = util.bin2int(msgbin[57:67])
|
||||
|
||||
tag = 'AS'
|
||||
|
||||
vr_sign = -1 if int(msgbin[68]) else 1
|
||||
vr = (util.bin2int(msgbin[69:78]) - 1) * 64 # vertical rate, fpm
|
||||
rocd = vr_sign * vr
|
||||
|
||||
return int(spd), round(hdg, 1), int(rocd), tag
|
||||
|
||||
|
||||
def surface_velocity(msg):
|
||||
"""Decode surface velocity from from a surface position message
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
(int, float, int, string): speed (kt), heading (degree),
|
||||
rate of climb/descend (ft/min), and speed type
|
||||
('GS' for ground speed, 'AS' for airspeed)
|
||||
"""
|
||||
|
||||
if typecode(msg) < 5 or typecode(msg) > 8:
|
||||
raise RuntimeError("%s: Not a surface message, expecting 5<TC<8" % msg)
|
||||
|
||||
msgbin = util.hex2bin(msg)
|
||||
|
||||
# heading
|
||||
hdg_status = int(msgbin[44])
|
||||
if hdg_status == 1:
|
||||
hdg = util.bin2int(msgbin[45:52]) * 360.0 / 128.0
|
||||
hdg = round(hdg, 1)
|
||||
else:
|
||||
hdg = None
|
||||
|
||||
# ground movment / speed
|
||||
mov = util.bin2int(msgbin[37:44])
|
||||
|
||||
if mov == 0 or mov > 124:
|
||||
spd = None
|
||||
elif mov == 1:
|
||||
spd = 0
|
||||
elif mov == 124:
|
||||
spd = 175
|
||||
else:
|
||||
movs = [2, 9, 13, 39, 94, 109, 124]
|
||||
kts = [0.125, 1, 2, 15, 70, 100, 175]
|
||||
i = next(m[0] for m in enumerate(movs) if m[1] > mov)
|
||||
step = (kts[i] - kts[i-1]) * 1.0 / (movs[i]-movs[i-1])
|
||||
spd = kts[i-1] + (mov-movs[i-1]) * step
|
||||
spd = round(spd, 2)
|
||||
|
||||
return spd, hdg, 0, 'GS'
|
||||
|
||||
def altitude_diff(msg):
|
||||
"""Decode the differece between GNSS and barometric altitude
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string, TC=19
|
||||
|
||||
Returns:
|
||||
int: Altitude difference in ft. Negative value indicates GNSS altitude
|
||||
below barometric altitude.
|
||||
"""
|
||||
|
||||
if typecode(msg) != 19:
|
||||
raise RuntimeError("incorrect message types, expecting TC=19")
|
||||
|
||||
msgbin = util.hex2bin(msg)
|
||||
sign = -1 if int(msgbin[80]) else 1
|
||||
value = util.bin2int(msgbin[81:88])
|
||||
|
||||
if value == 0 or value == 127:
|
||||
return None
|
||||
else:
|
||||
return sign * (value - 1) * 25 # in ft.
|
||||
3
pyModeS/decoder/__init__.py
Normal file
3
pyModeS/decoder/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from __future__ import absolute_import, print_function, division
|
||||
|
||||
from pyModeS.decoder import *
|
||||
23
pyModeS/decoder/acas.py
Normal file
23
pyModeS/decoder/acas.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# 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
|
||||
|
||||
[To be implemented]
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
from pyModeS.decoder import common
|
||||
523
pyModeS/decoder/adsb.py
Normal file
523
pyModeS/decoder/adsb.py
Normal file
@@ -0,0 +1,523 @@
|
||||
# Copyright (C) 2015 Junzi Sun (TU Delft)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""ADS-B Wrapper.
|
||||
|
||||
The ADS-B wrapper also imports functions from the following modules:
|
||||
|
||||
- pyModeS.decoder.bds.bds05
|
||||
Functions: ``airborne_position``, ``airborne_position_with_ref``, ``altitude``
|
||||
- pyModeS.decoder.bds.bds06
|
||||
Functions: ``surface_position``, ``surface_position_with_ref``, ``surface_velocity``
|
||||
- pyModeS.decoder.bds.bds08
|
||||
Functions: ``category``, ``callsign``
|
||||
- pyModeS.decoder.bds.bds09
|
||||
Functions: ``airborne_velocity``, ``altitude_diff``
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
|
||||
import pyModeS as pms
|
||||
from pyModeS.decoder import common
|
||||
from pyModeS.decoder import uncertainty
|
||||
|
||||
# from pyModeS.decoder.bds import bds05, bds06, bds09
|
||||
from pyModeS.decoder.bds.bds05 import airborne_position, airborne_position_with_ref, altitude
|
||||
from pyModeS.decoder.bds.bds06 import surface_position, surface_position_with_ref, surface_velocity
|
||||
from pyModeS.decoder.bds.bds08 import category, callsign
|
||||
from pyModeS.decoder.bds.bds09 import airborne_velocity, altitude_diff
|
||||
|
||||
def df(msg):
|
||||
return common.df(msg)
|
||||
|
||||
def icao(msg):
|
||||
return common.icao(msg)
|
||||
|
||||
def typecode(msg):
|
||||
return common.typecode(msg)
|
||||
|
||||
def position(msg0, msg1, t0, t1, lat_ref=None, lon_ref=None):
|
||||
"""Decode position from a pair of even and odd position message
|
||||
(works with both airborne and surface position messages)
|
||||
|
||||
Args:
|
||||
msg0 (string): even message (28 bytes hexadecimal string)
|
||||
msg1 (string): odd message (28 bytes hexadecimal string)
|
||||
t0 (int): timestamps for the even message
|
||||
t1 (int): timestamps for the odd message
|
||||
|
||||
Returns:
|
||||
(float, float): (latitude, longitude) of the aircraft
|
||||
"""
|
||||
tc0 = typecode(msg0)
|
||||
tc1 = typecode(msg1)
|
||||
|
||||
if (5<=tc0<=8 and 5<=tc1<=8):
|
||||
if (not lat_ref) or (not lon_ref):
|
||||
raise RuntimeError("Surface position encountered, a reference \
|
||||
position lat/lon required. Location of \
|
||||
receiver can be used.")
|
||||
else:
|
||||
return surface_position(msg0, msg1, t0, t1, lat_ref, lon_ref)
|
||||
|
||||
elif (9<=tc0<=18 and 9<=tc1<=18):
|
||||
# Airborne position with barometric height
|
||||
return airborne_position(msg0, msg1, t0, t1)
|
||||
|
||||
elif (20<=tc0<=22 and 20<=tc1<=22):
|
||||
# Airborne position with GNSS height
|
||||
return airborne_position(msg0, msg1, t0, t1)
|
||||
|
||||
else:
|
||||
raise RuntimeError("incorrect or inconsistant message types")
|
||||
|
||||
|
||||
def position_with_ref(msg, lat_ref, lon_ref):
|
||||
"""Decode position with only one message,
|
||||
knowing reference nearby location, such as previously
|
||||
calculated location, ground station, or airport location, etc.
|
||||
Works with both airborne and surface position messages.
|
||||
The reference position shall be with in 180NM (airborne) or 45NM (surface)
|
||||
of the true position.
|
||||
|
||||
Args:
|
||||
msg (string): even message (28 bytes hexadecimal string)
|
||||
lat_ref: previous known latitude
|
||||
lon_ref: previous known longitude
|
||||
|
||||
Returns:
|
||||
(float, float): (latitude, longitude) of the aircraft
|
||||
"""
|
||||
|
||||
tc = typecode(msg)
|
||||
|
||||
if 5<=tc<=8:
|
||||
return surface_position_with_ref(msg, lat_ref, lon_ref)
|
||||
|
||||
elif 9<=tc<=18 or 20<=tc<=22:
|
||||
return airborne_position_with_ref(msg, lat_ref, lon_ref)
|
||||
|
||||
else:
|
||||
raise RuntimeError("incorrect or inconsistant message types")
|
||||
|
||||
|
||||
def altitude(msg):
|
||||
"""Decode aircraft altitude
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
int: altitude in feet
|
||||
"""
|
||||
|
||||
tc = typecode(msg)
|
||||
|
||||
if tc<5 or tc==19 or tc>22:
|
||||
raise RuntimeError("%s: Not a position message" % msg)
|
||||
|
||||
if tc>=5 and tc<=8:
|
||||
# surface position, altitude 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:
|
||||
return None
|
||||
|
||||
|
||||
def velocity(msg, rtn_sources=False):
|
||||
"""Calculate the speed, heading, and vertical rate
|
||||
(handles both airborne or surface message)
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
rtn_source (boolean): If the function will return
|
||||
the sources for direction of travel and vertical
|
||||
rate. This will change the return value from a four
|
||||
element array to a six element array.
|
||||
|
||||
Returns:
|
||||
(int, float, int, string, string, string): speed (kt),
|
||||
ground track or heading (degree),
|
||||
rate of climb/descent (ft/min), speed type
|
||||
('GS' for ground speed, 'AS' for airspeed),
|
||||
direction source ('true_north' for ground track / true north
|
||||
as refrence, 'mag_north' for magnetic north as reference),
|
||||
rate of climb/descent source ('Baro' for barometer, 'GNSS'
|
||||
for GNSS constellation).
|
||||
|
||||
In the case of surface messages, None will be put in place
|
||||
for vertical rate and its respective sources.
|
||||
"""
|
||||
|
||||
if 5 <= typecode(msg) <= 8:
|
||||
return surface_velocity(msg, rtn_sources)
|
||||
|
||||
elif typecode(msg) == 19:
|
||||
return airborne_velocity(msg, rtn_sources)
|
||||
|
||||
else:
|
||||
raise RuntimeError("incorrect or inconsistant message types, expecting 4<TC<9 or TC=19")
|
||||
|
||||
|
||||
def speed_heading(msg):
|
||||
"""Get speed and ground track (or heading) from the velocity message
|
||||
(handles both airborne or surface message)
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
(int, float): speed (kt), ground track or heading (degree)
|
||||
"""
|
||||
spd, trk_or_hdg, rocd, tag = velocity(msg)
|
||||
return spd, trk_or_hdg
|
||||
|
||||
|
||||
def oe_flag(msg):
|
||||
"""Check the odd/even flag. Bit 54, 0 for even, 1 for odd.
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
Returns:
|
||||
int: 0 or 1, for even or odd frame
|
||||
"""
|
||||
msgbin = common.hex2bin(msg)
|
||||
return int(msgbin[53])
|
||||
|
||||
|
||||
def version(msg):
|
||||
"""ADS-B Version
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string, TC = 31
|
||||
|
||||
Returns:
|
||||
int: version number
|
||||
"""
|
||||
tc = typecode(msg)
|
||||
|
||||
if tc != 31:
|
||||
raise RuntimeError("%s: Not a status operation message, expecting TC = 31" % msg)
|
||||
|
||||
msgbin = common.hex2bin(msg)
|
||||
version = common.bin2int(msgbin[72:75])
|
||||
|
||||
return version
|
||||
|
||||
|
||||
def nuc_p(msg):
|
||||
"""Calculate NUCp, Navigation Uncertainty Category - Position (ADS-B version 1)
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string,
|
||||
|
||||
Returns:
|
||||
int: Horizontal Protection Limit
|
||||
int: 95% Containment Radius - Horizontal (meters)
|
||||
int: 95% Containment Radius - Vertical (meters)
|
||||
|
||||
"""
|
||||
tc = typecode(msg)
|
||||
|
||||
if typecode(msg) < 5 or typecode(msg) > 22:
|
||||
raise RuntimeError(
|
||||
"%s: Not a surface position message (5<TC<8), \
|
||||
airborne position message (8<TC<19), \
|
||||
or airborne position with GNSS height (20<TC<22)" % msg
|
||||
)
|
||||
|
||||
try:
|
||||
NUCp = uncertainty.TC_NUCp_lookup[tc]
|
||||
HPL = uncertainty.NUCp[NUCp]['HPL']
|
||||
RCu = uncertainty.NUCp[NUCp]['RCu']
|
||||
RCv = uncertainty.NUCp[NUCp]['RCv']
|
||||
except KeyError:
|
||||
HPL, RCu, RCv = uncertainty.NA, uncertainty.NA, uncertainty.NA
|
||||
|
||||
|
||||
if tc in [20, 21]:
|
||||
RCv = uncertainty.NA
|
||||
|
||||
return HPL, RCu, RCv
|
||||
|
||||
|
||||
def nuc_v(msg):
|
||||
"""Calculate NUCv, Navigation Uncertainty Category - Velocity (ADS-B version 1)
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string,
|
||||
|
||||
Returns:
|
||||
int or string: 95% Horizontal Velocity Error
|
||||
int or string: 95% Vertical Velocity Error
|
||||
"""
|
||||
tc = typecode(msg)
|
||||
|
||||
if tc != 19:
|
||||
raise RuntimeError("%s: Not an airborne velocity message, expecting TC = 19" % msg)
|
||||
|
||||
|
||||
msgbin = common.hex2bin(msg)
|
||||
NUCv = common.bin2int(msgbin[42:45])
|
||||
|
||||
try:
|
||||
HVE = uncertainty.NUCv[NUCv]['HVE']
|
||||
VVE = uncertainty.NUCv[NUCv]['VVE']
|
||||
except KeyError:
|
||||
HVE, VVE = uncertainty.NA, uncertainty.NA
|
||||
|
||||
return HVE, VVE
|
||||
|
||||
|
||||
def nic_v1(msg, NICs):
|
||||
"""Calculate NIC, navigation integrity category, for ADS-B version 1
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
NICs (int or string): NIC supplement
|
||||
|
||||
Returns:
|
||||
int or string: Horizontal Radius of Containment
|
||||
int or string: Vertical Protection Limit
|
||||
"""
|
||||
if typecode(msg) < 5 or typecode(msg) > 22:
|
||||
raise RuntimeError(
|
||||
"%s: Not a surface position message (5<TC<8), \
|
||||
airborne position message (8<TC<19), \
|
||||
or airborne position with GNSS height (20<TC<22)" % msg
|
||||
)
|
||||
|
||||
tc = typecode(msg)
|
||||
NIC = uncertainty.TC_NICv1_lookup[tc]
|
||||
|
||||
if isinstance(NIC, dict):
|
||||
NIC = NIC[NICs]
|
||||
|
||||
try:
|
||||
Rc = uncertainty.NICv1[NIC][NICs]['Rc']
|
||||
VPL = uncertainty.NICv1[NIC][NICs]['VPL']
|
||||
except KeyError:
|
||||
Rc, VPL = uncertainty.NA, uncertainty.NA
|
||||
|
||||
return Rc, VPL
|
||||
|
||||
|
||||
def nic_v2(msg, NICa, NICbc):
|
||||
"""Calculate NIC, navigation integrity category, for ADS-B version 2
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
NICa (int or string): NIC supplement - A
|
||||
NICbc (int or srting): NIC supplement - B or C
|
||||
|
||||
Returns:
|
||||
int or string: Horizontal Radius of Containment
|
||||
"""
|
||||
if typecode(msg) < 5 or typecode(msg) > 22:
|
||||
raise RuntimeError(
|
||||
"%s: Not a surface position message (5<TC<8), \
|
||||
airborne position message (8<TC<19), \
|
||||
or airborne position with GNSS height (20<TC<22)" % msg
|
||||
)
|
||||
|
||||
tc = typecode(msg)
|
||||
NIC = uncertainty.TC_NICv2_lookup[tc]
|
||||
|
||||
if 20<=tc<=22:
|
||||
NICs = 0
|
||||
else:
|
||||
NICs = NICa*2 + NICbc
|
||||
|
||||
try:
|
||||
if isinstance(NIC, dict):
|
||||
NIC = NIC[NICs]
|
||||
|
||||
Rc = uncertainty.NICv2[NIC][NICs]['Rc']
|
||||
except KeyError:
|
||||
Rc = uncertainty.NA
|
||||
|
||||
return Rc
|
||||
|
||||
|
||||
def nic_s(msg):
|
||||
"""Obtain NIC supplement bit, TC=31 message
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
int: NICs number (0 or 1)
|
||||
"""
|
||||
tc = typecode(msg)
|
||||
|
||||
if tc != 31:
|
||||
raise RuntimeError("%s: Not a status operation message, expecting TC = 31" % msg)
|
||||
|
||||
msgbin = common.hex2bin(msg)
|
||||
nic_s = int(msgbin[75])
|
||||
|
||||
return nic_s
|
||||
|
||||
|
||||
def nic_a_c(msg):
|
||||
"""Obtain NICa/c, navigation integrity category supplements a and c
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
(int, int): NICa and NICc number (0 or 1)
|
||||
"""
|
||||
tc = typecode(msg)
|
||||
|
||||
if tc != 31:
|
||||
raise RuntimeError("%s: Not a status operation message, expecting TC = 31" % msg)
|
||||
|
||||
msgbin = common.hex2bin(msg)
|
||||
nic_a = int(msgbin[75])
|
||||
nic_c = int(msgbin[51])
|
||||
|
||||
return nic_a, nic_c
|
||||
|
||||
|
||||
def nic_b(msg):
|
||||
"""Obtain NICb, navigation integrity category supplement-b
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
int: NICb number (0 or 1)
|
||||
"""
|
||||
tc = typecode(msg)
|
||||
|
||||
if tc < 9 or tc > 18:
|
||||
raise RuntimeError("%s: Not a airborne position message, expecting 8<TC<19" % msg)
|
||||
|
||||
msgbin = common.hex2bin(msg)
|
||||
nic_b = int(msgbin[39])
|
||||
|
||||
return nic_b
|
||||
|
||||
|
||||
def nac_p(msg):
|
||||
"""Calculate NACp, Navigation Accuracy Category - Position
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string, TC = 29 or 31
|
||||
|
||||
Returns:
|
||||
int or string: 95% horizontal accuracy bounds, Estimated Position Uncertainty
|
||||
int or string: 95% vertical accuracy bounds, Vertical Estimated Position Uncertainty
|
||||
"""
|
||||
tc = typecode(msg)
|
||||
|
||||
if tc not in [29, 31]:
|
||||
raise RuntimeError("%s: Not a target state and status message, \
|
||||
or operation status message, expecting TC = 29 or 31" % msg)
|
||||
|
||||
msgbin = common.hex2bin(msg)
|
||||
|
||||
if tc == 29:
|
||||
NACp = common.bin2int(msgbin[71:75])
|
||||
elif tc == 31:
|
||||
NACp = common.bin2int(msgbin[76:80])
|
||||
|
||||
try:
|
||||
EPU = uncertainty.NACp[NACp]['EPU']
|
||||
VEPU = uncertainty.NACp[NACp]['VEPU']
|
||||
except KeyError:
|
||||
EPU, VEPU = uncertainty.NA, uncertainty.NA
|
||||
|
||||
return EPU, VEPU
|
||||
|
||||
|
||||
def nac_v(msg):
|
||||
"""Calculate NACv, Navigation Accuracy Category - Velocity
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string, TC = 19
|
||||
|
||||
Returns:
|
||||
int or string: 95% horizontal accuracy bounds for velocity, Horizontal Figure of Merit
|
||||
int or string: 95% vertical accuracy bounds for velocity, Vertical Figure of Merit
|
||||
"""
|
||||
tc = typecode(msg)
|
||||
|
||||
if tc != 19:
|
||||
raise RuntimeError("%s: Not an airborne velocity message, expecting TC = 19" % msg)
|
||||
|
||||
msgbin = common.hex2bin(msg)
|
||||
NACv = common.bin2int(msgbin[42:45])
|
||||
|
||||
try:
|
||||
HFOMr = uncertainty.NACv[NACv]['HFOMr']
|
||||
VFOMr = uncertainty.NACv[NACv]['VFOMr']
|
||||
except KeyError:
|
||||
HFOMr, VFOMr = uncertainty.NA, uncertainty.NA
|
||||
|
||||
return HFOMr, VFOMr
|
||||
|
||||
|
||||
def sil(msg, version):
|
||||
"""Calculate SIL, Surveillance Integrity Level
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string with TC = 29, 31
|
||||
|
||||
Returns:
|
||||
int or string: Probability of exceeding Horizontal Radius of Containment RCu
|
||||
int or string: Probability of exceeding Vertical Integrity Containment Region VPL
|
||||
string: SIL supplement based on per "hour" or "sample", or 'unknown'
|
||||
"""
|
||||
tc = typecode(msg)
|
||||
|
||||
if tc not in [29, 31]:
|
||||
raise RuntimeError("%s: Not a target state and status messag, \
|
||||
or operation status message, expecting TC = 29 or 31" % msg)
|
||||
|
||||
msgbin = common.hex2bin(msg)
|
||||
|
||||
if tc == 29:
|
||||
SIL = common.bin2int(msgbin[76:78])
|
||||
elif tc == 31:
|
||||
SIL = common.bin2int(msgbin[82:84])
|
||||
|
||||
try:
|
||||
PE_RCu = uncertainty.SIL[SIL]['PE_RCu']
|
||||
PE_VPL = uncertainty.SIL[SIL]['PE_VPL']
|
||||
except KeyError:
|
||||
PE_RCu, PE_VPL = uncertainty.NA, uncertainty.NA
|
||||
|
||||
base = 'unknown'
|
||||
|
||||
if version == 2:
|
||||
if tc == 29:
|
||||
SIL_SUP = common.bin2int(msgbin[39])
|
||||
elif tc == 31:
|
||||
SIL_SUP = common.bin2int(msgbin[86])
|
||||
|
||||
if SIL_SUP == 0:
|
||||
base = "hour"
|
||||
elif SIL_SUP == 1:
|
||||
base = "sample"
|
||||
|
||||
return PE_RCu, PE_VPL, base
|
||||
23
pyModeS/decoder/allcall.py
Normal file
23
pyModeS/decoder/allcall.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# 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
|
||||
|
||||
[To be implemented]
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
from pyModeS.decoder import common
|
||||
154
pyModeS/decoder/bds/__init__.py
Normal file
154
pyModeS/decoder/bds/__init__.py
Normal file
@@ -0,0 +1,154 @@
|
||||
# Copyright (C) 2015 Junzi Sun (TU Delft)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
"""
|
||||
Common functions for Mode-S decoding
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
import numpy as np
|
||||
|
||||
from pyModeS.extra import aero
|
||||
from pyModeS.decoder import common
|
||||
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):
|
||||
"""Use reference ground speed and trk to determine BDS50 and DBS60.
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
spd_ref (float): reference speed (ADS-B ground speed), kts
|
||||
trk_ref (float): reference track (ADS-B track angle), deg
|
||||
alt_ref (float): reference altitude (ADS-B altitude), ft
|
||||
|
||||
Returns:
|
||||
String or None: BDS version, or possible versions, or None if nothing matches.
|
||||
|
||||
"""
|
||||
def vxy(v, angle):
|
||||
vx = v * np.sin(np.radians(angle))
|
||||
vy = v * np.cos(np.radians(angle))
|
||||
return vx, vy
|
||||
|
||||
if not (bds50.is50(msg) and bds60.is60(msg)):
|
||||
return None
|
||||
|
||||
h50 = bds50.trk50(msg)
|
||||
v50 = bds50.gs50(msg)
|
||||
|
||||
if h50 is None or v50 is None:
|
||||
return 'BDS50,BDS60'
|
||||
|
||||
h60 = bds60.hdg60(msg)
|
||||
m60 = bds60.mach60(msg)
|
||||
i60 = bds60.ias60(msg)
|
||||
|
||||
if h60 is None or (m60 is None and i60 is None):
|
||||
return 'BDS50,BDS60'
|
||||
|
||||
m60 = np.nan if m60 is None else m60
|
||||
i60 = np.nan if i60 is None else i60
|
||||
|
||||
XY5 = vxy(v50*aero.kts, h50)
|
||||
XY6m = vxy(aero.mach2tas(m60, alt_ref*aero.ft), h60)
|
||||
XY6i = vxy(aero.cas2tas(i60*aero.kts, alt_ref*aero.ft), h60)
|
||||
|
||||
allbds = ['BDS50', 'BDS60', 'BDS60']
|
||||
|
||||
X = np.array([XY5, XY6m, XY6i])
|
||||
Mu = np.array(vxy(spd_ref*aero.kts, trk_ref))
|
||||
|
||||
# compute Mahalanobis distance matrix
|
||||
# Cov = [[20**2, 0], [0, 20**2]]
|
||||
# mmatrix = np.sqrt(np.dot(np.dot(X-Mu, np.linalg.inv(Cov)), (X-Mu).T))
|
||||
# dist = np.diag(mmatrix)
|
||||
|
||||
# since the covariance matrix is identity matrix,
|
||||
# M-dist is same as eculidian distance
|
||||
try:
|
||||
dist = np.linalg.norm(X-Mu, axis=1)
|
||||
BDS = allbds[np.nanargmin(dist)]
|
||||
except ValueError:
|
||||
return 'BDS50,BDS60'
|
||||
|
||||
return BDS
|
||||
|
||||
|
||||
def infer(msg, mrar=False):
|
||||
"""Estimate the most likely BDS code of an message.
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
mrar (bool): Also infer MRAR (BDS 44) and MHR (BDS 45). Defaults to False.
|
||||
|
||||
Returns:
|
||||
String or None: BDS version, or possible versions, or None if nothing matches.
|
||||
|
||||
"""
|
||||
df = common.df(msg)
|
||||
|
||||
if common.allzeros(msg):
|
||||
return 'EMPTY'
|
||||
|
||||
# For ADS-B / Mode-S extended squitter
|
||||
if df == 17:
|
||||
tc = common.typecode(msg)
|
||||
|
||||
if 1 <= tc <= 4:
|
||||
return 'BDS08' # indentification and category
|
||||
if 5 <= tc <= 8:
|
||||
return 'BDS06' # surface movement
|
||||
if 9 <= tc <= 18:
|
||||
return 'BDS05' # airborne position, baro-alt
|
||||
if tc == 19:
|
||||
return 'BDS09' # airborne velocity
|
||||
if 20 <= tc <= 22:
|
||||
return 'BDS05' # airborne position, gnss-alt
|
||||
if tc == 28:
|
||||
return 'BDS61' # aircraft status
|
||||
if tc == 29:
|
||||
return 'BDS62' # target state and status
|
||||
if tc == 31:
|
||||
return 'BDS65' # operational status
|
||||
|
||||
# For Comm-B replies
|
||||
IS10 = bds10.is10(msg)
|
||||
IS17 = bds17.is17(msg)
|
||||
IS20 = bds20.is20(msg)
|
||||
IS30 = bds30.is30(msg)
|
||||
IS40 = bds40.is40(msg)
|
||||
IS50 = bds50.is50(msg)
|
||||
IS60 = bds60.is60(msg)
|
||||
IS44 = bds44.is44(msg)
|
||||
IS45 = bds45.is45(msg)
|
||||
|
||||
if mrar:
|
||||
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]
|
||||
|
||||
bds = ','.join(sorted(allbds[mask]))
|
||||
|
||||
if len(bds) == 0:
|
||||
return None
|
||||
else:
|
||||
return bds
|
||||
161
pyModeS/decoder/bds/bds05.py
Normal file
161
pyModeS/decoder/bds/bds05.py
Normal file
@@ -0,0 +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/>.
|
||||
|
||||
|
||||
# ------------------------------------------
|
||||
# BDS 0,5
|
||||
# ADS-B TC=9-18
|
||||
# Airborn position
|
||||
# ------------------------------------------
|
||||
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
from pyModeS.decoder import common
|
||||
|
||||
def airborne_position(msg0, msg1, t0, t1):
|
||||
"""Decode airborn position from a pair of even and odd position message
|
||||
|
||||
Args:
|
||||
msg0 (string): even message (28 bytes hexadecimal string)
|
||||
msg1 (string): odd message (28 bytes hexadecimal string)
|
||||
t0 (int): timestamps for the even message
|
||||
t1 (int): timestamps for the odd message
|
||||
|
||||
Returns:
|
||||
(float, float): (latitude, longitude) of the aircraft
|
||||
"""
|
||||
|
||||
mb0 = common.hex2bin(msg0)[32:]
|
||||
mb1 = common.hex2bin(msg1)[32:]
|
||||
|
||||
# 131072 is 2^17, since CPR lat and lon are 17 bits each.
|
||||
cprlat_even = common.bin2int(mb0[22:39]) / 131072.0
|
||||
cprlon_even = common.bin2int(mb0[39:56]) / 131072.0
|
||||
cprlat_odd = common.bin2int(mb1[22:39]) / 131072.0
|
||||
cprlon_odd = common.bin2int(mb1[39:56]) / 131072.0
|
||||
|
||||
air_d_lat_even = 360.0 / 60
|
||||
air_d_lat_odd = 360.0 / 59
|
||||
|
||||
# compute latitude index 'j'
|
||||
j = common.floor(59 * cprlat_even - 60 * cprlat_odd + 0.5)
|
||||
|
||||
lat_even = float(air_d_lat_even * (j % 60 + cprlat_even))
|
||||
lat_odd = float(air_d_lat_odd * (j % 59 + cprlat_odd))
|
||||
|
||||
if lat_even >= 270:
|
||||
lat_even = lat_even - 360
|
||||
|
||||
if lat_odd >= 270:
|
||||
lat_odd = lat_odd - 360
|
||||
|
||||
# check if both are in the same latidude zone, exit if not
|
||||
if common.cprNL(lat_even) != common.cprNL(lat_odd):
|
||||
return None
|
||||
|
||||
# compute ni, longitude index m, and longitude
|
||||
if (t0 > t1):
|
||||
lat = lat_even
|
||||
nl = common.cprNL(lat)
|
||||
ni = max(common.cprNL(lat)- 0, 1)
|
||||
m = common.floor(cprlon_even * (nl-1) - cprlon_odd * nl + 0.5)
|
||||
lon = (360.0 / ni) * (m % ni + cprlon_even)
|
||||
else:
|
||||
lat = lat_odd
|
||||
nl = common.cprNL(lat)
|
||||
ni = max(common.cprNL(lat) - 1, 1)
|
||||
m = common.floor(cprlon_even * (nl-1) - cprlon_odd * nl + 0.5)
|
||||
lon = (360.0 / ni) * (m % ni + cprlon_odd)
|
||||
|
||||
if lon > 180:
|
||||
lon = lon - 360
|
||||
|
||||
return round(lat, 5), round(lon, 5)
|
||||
|
||||
|
||||
def airborne_position_with_ref(msg, lat_ref, lon_ref):
|
||||
"""Decode airborne position with only one message,
|
||||
knowing reference nearby location, such as previously calculated location,
|
||||
ground station, or airport location, etc. The reference position shall
|
||||
be with in 180NM of the true position.
|
||||
|
||||
Args:
|
||||
msg (string): even message (28 bytes hexadecimal string)
|
||||
lat_ref: previous known latitude
|
||||
lon_ref: previous known longitude
|
||||
|
||||
Returns:
|
||||
(float, float): (latitude, longitude) of the aircraft
|
||||
"""
|
||||
|
||||
|
||||
mb = common.hex2bin(msg)[32:]
|
||||
|
||||
cprlat = common.bin2int(mb[22:39]) / 131072.0
|
||||
cprlon = common.bin2int(mb[39:56]) / 131072.0
|
||||
|
||||
i = int(mb[21])
|
||||
d_lat = 360.0/59 if i else 360.0/60
|
||||
|
||||
j = common.floor(lat_ref / d_lat) \
|
||||
+ common.floor(0.5 + ((lat_ref % d_lat) / d_lat) - cprlat)
|
||||
|
||||
lat = d_lat * (j + cprlat)
|
||||
|
||||
ni = common.cprNL(lat) - i
|
||||
|
||||
if ni > 0:
|
||||
d_lon = 360.0 / ni
|
||||
else:
|
||||
d_lon = 360.0
|
||||
|
||||
m = common.floor(lon_ref / d_lon) \
|
||||
+ common.floor(0.5 + ((lon_ref % d_lon) / d_lon) - cprlon)
|
||||
|
||||
lon = d_lon * (m + cprlon)
|
||||
|
||||
return round(lat, 5), round(lon, 5)
|
||||
|
||||
|
||||
def altitude(msg):
|
||||
"""Decode aircraft altitude
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
int: altitude in feet
|
||||
"""
|
||||
|
||||
tc = common.typecode(msg)
|
||||
|
||||
if tc<9 or tc==19 or tc>22:
|
||||
raise RuntimeError("%s: Not a airborn position message" % msg)
|
||||
|
||||
mb = common.hex2bin(msg)[32:]
|
||||
|
||||
if tc < 19:
|
||||
# barometric altitude
|
||||
q = mb[15]
|
||||
if q:
|
||||
n = common.bin2int(mb[8:15]+mb[16:20])
|
||||
alt = n * 25 - 1000
|
||||
else:
|
||||
alt = None
|
||||
else:
|
||||
# GNSS altitude, meters -> feet
|
||||
alt = common.bin2int(mb[8:20]) * 3.28084
|
||||
|
||||
return alt
|
||||
194
pyModeS/decoder/bds/bds06.py
Normal file
194
pyModeS/decoder/bds/bds06.py
Normal file
@@ -0,0 +1,194 @@
|
||||
# Copyright (C) 2018 Junzi Sun (TU Delft)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
# ------------------------------------------
|
||||
# BDS 0,6
|
||||
# ADS-B TC=5-8
|
||||
# Surface position
|
||||
# ------------------------------------------
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
from pyModeS.decoder import common
|
||||
import math
|
||||
|
||||
|
||||
def surface_position(msg0, msg1, t0, t1, lat_ref, lon_ref):
|
||||
"""Decode surface position from a pair of even and odd position message,
|
||||
the lat/lon of receiver must be provided to yield the correct solution.
|
||||
|
||||
Args:
|
||||
msg0 (string): even message (28 bytes hexadecimal string)
|
||||
msg1 (string): odd message (28 bytes hexadecimal string)
|
||||
t0 (int): timestamps for the even message
|
||||
t1 (int): timestamps for the odd message
|
||||
lat_ref (float): latitude of the receiver
|
||||
lon_ref (float): longitude of the receiver
|
||||
|
||||
Returns:
|
||||
(float, float): (latitude, longitude) of the aircraft
|
||||
"""
|
||||
|
||||
msgbin0 = common.hex2bin(msg0)
|
||||
msgbin1 = common.hex2bin(msg1)
|
||||
|
||||
# 131072 is 2^17, since CPR lat and lon are 17 bits each.
|
||||
cprlat_even = common.bin2int(msgbin0[54:71]) / 131072.0
|
||||
cprlon_even = common.bin2int(msgbin0[71:88]) / 131072.0
|
||||
cprlat_odd = common.bin2int(msgbin1[54:71]) / 131072.0
|
||||
cprlon_odd = common.bin2int(msgbin1[71:88]) / 131072.0
|
||||
|
||||
air_d_lat_even = 90.0 / 60
|
||||
air_d_lat_odd = 90.0 / 59
|
||||
|
||||
# compute latitude index 'j'
|
||||
j = common.floor(59 * cprlat_even - 60 * cprlat_odd + 0.5)
|
||||
|
||||
# solution for north hemisphere
|
||||
lat_even_n = float(air_d_lat_even * (j % 60 + cprlat_even))
|
||||
lat_odd_n = float(air_d_lat_odd * (j % 59 + cprlat_odd))
|
||||
|
||||
# solution for north hemisphere
|
||||
lat_even_s = lat_even_n - 90.0
|
||||
lat_odd_s = lat_odd_n - 90.0
|
||||
|
||||
# chose which solution corrispondes to receiver location
|
||||
lat_even = lat_even_n if lat_ref > 0 else lat_even_s
|
||||
lat_odd = lat_odd_n if lat_ref > 0 else lat_odd_s
|
||||
|
||||
# check if both are in the same latidude zone, rare but possible
|
||||
if common.cprNL(lat_even) != common.cprNL(lat_odd):
|
||||
return None
|
||||
|
||||
# compute ni, longitude index m, and longitude
|
||||
if (t0 > t1):
|
||||
lat = lat_even
|
||||
nl = common.cprNL(lat_even)
|
||||
ni = max(common.cprNL(lat_even) - 0, 1)
|
||||
m = common.floor(cprlon_even * (nl-1) - cprlon_odd * nl + 0.5)
|
||||
lon = (90.0 / ni) * (m % ni + cprlon_even)
|
||||
else:
|
||||
lat = lat_odd
|
||||
nl = common.cprNL(lat_odd)
|
||||
ni = max(common.cprNL(lat_odd) - 1, 1)
|
||||
m = common.floor(cprlon_even * (nl-1) - cprlon_odd * nl + 0.5)
|
||||
lon = (90.0 / ni) * (m % ni + cprlon_odd)
|
||||
|
||||
# four possible longitude solutions
|
||||
lons = [lon, lon + 90.0, lon + 180.0, lon + 270.0]
|
||||
|
||||
# the closest solution to receiver is the correct one
|
||||
dls = [abs(lon_ref - l) for l in lons]
|
||||
imin = min(range(4), key=dls.__getitem__)
|
||||
lon = lons[imin]
|
||||
|
||||
return round(lat, 5), round(lon, 5)
|
||||
|
||||
|
||||
def surface_position_with_ref(msg, lat_ref, lon_ref):
|
||||
"""Decode surface position with only one message,
|
||||
knowing reference nearby location, such as previously calculated location,
|
||||
ground station, or airport location, etc. The reference position shall
|
||||
be with in 45NM of the true position.
|
||||
|
||||
Args:
|
||||
msg (string): even message (28 bytes hexadecimal string)
|
||||
lat_ref: previous known latitude
|
||||
lon_ref: previous known longitude
|
||||
|
||||
Returns:
|
||||
(float, float): (latitude, longitude) of the aircraft
|
||||
"""
|
||||
|
||||
|
||||
mb = common.hex2bin(msg)[32:]
|
||||
|
||||
cprlat = common.bin2int(mb[22:39]) / 131072.0
|
||||
cprlon = common.bin2int(mb[39:56]) / 131072.0
|
||||
|
||||
i = int(mb[21])
|
||||
d_lat = 90.0/59 if i else 90.0/60
|
||||
|
||||
j = common.floor(lat_ref / d_lat) \
|
||||
+ common.floor(0.5 + ((lat_ref % d_lat) / d_lat) - cprlat)
|
||||
|
||||
lat = d_lat * (j + cprlat)
|
||||
|
||||
ni = common.cprNL(lat) - i
|
||||
|
||||
if ni > 0:
|
||||
d_lon = 90.0 / ni
|
||||
else:
|
||||
d_lon = 90.0
|
||||
|
||||
m = common.floor(lon_ref / d_lon) \
|
||||
+ common.floor(0.5 + ((lon_ref % d_lon) / d_lon) - cprlon)
|
||||
|
||||
lon = d_lon * (m + cprlon)
|
||||
|
||||
return round(lat, 5), round(lon, 5)
|
||||
|
||||
|
||||
def surface_velocity(msg, rtn_sources=False):
|
||||
"""Decode surface velocity from from a surface position message
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
rtn_source (boolean): If the function will return
|
||||
the sources for direction of travel and vertical
|
||||
rate. This will change the return value from a four
|
||||
element array to a six element array.
|
||||
|
||||
Returns:
|
||||
(int, float, int, string, string, None): speed (kt),
|
||||
ground track (degree), None for rate of climb/descend (ft/min),
|
||||
and speed type ('GS' for ground speed), direction source
|
||||
('true_north' for ground track / true north as reference),
|
||||
None rate of climb/descent source.
|
||||
"""
|
||||
|
||||
if common.typecode(msg) < 5 or common.typecode(msg) > 8:
|
||||
raise RuntimeError("%s: Not a surface message, expecting 5<TC<8" % msg)
|
||||
|
||||
mb = common.hex2bin(msg)[32:]
|
||||
|
||||
# ground track
|
||||
trk_status = int(mb[12])
|
||||
if trk_status == 1:
|
||||
trk = common.bin2int(mb[13:20]) * 360.0 / 128.0
|
||||
trk = round(trk, 1)
|
||||
else:
|
||||
trk = None
|
||||
|
||||
# ground movment / speed
|
||||
mov = common.bin2int(mb[5:12])
|
||||
|
||||
if mov == 0 or mov > 124:
|
||||
spd = None
|
||||
elif mov == 1:
|
||||
spd = 0
|
||||
elif mov == 124:
|
||||
spd = 175
|
||||
else:
|
||||
movs = [2, 9, 13, 39, 94, 109, 124]
|
||||
kts = [0.125, 1, 2, 15, 70, 100, 175]
|
||||
i = next(m[0] for m in enumerate(movs) if m[1] > mov)
|
||||
step = (kts[i] - kts[i-1]) * 1.0 / (movs[i]-movs[i-1])
|
||||
spd = kts[i-1] + (mov-movs[i-1]) * step
|
||||
spd = round(spd, 2)
|
||||
|
||||
if rtn_sources:
|
||||
return spd, trk, 0, 'GS', 'true_north', None
|
||||
else:
|
||||
return spd, trk, 0, 'GS'
|
||||
73
pyModeS/decoder/bds/bds08.py
Normal file
73
pyModeS/decoder/bds/bds08.py
Normal file
@@ -0,0 +1,73 @@
|
||||
# Copyright (C) 2018 Junzi Sun (TU Delft)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
# ------------------------------------------
|
||||
# BDS 0,8
|
||||
# ADS-B TC=1-4
|
||||
# Aircraft identitification and category
|
||||
# ------------------------------------------
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
from pyModeS.decoder import common
|
||||
|
||||
def category(msg):
|
||||
"""Aircraft category number
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
int: category number
|
||||
"""
|
||||
|
||||
if common.typecode(msg) < 1 or common.typecode(msg) > 4:
|
||||
raise RuntimeError("%s: Not a identification message" % msg)
|
||||
|
||||
msgbin = common.hex2bin(msg)
|
||||
return common.bin2int(msgbin[5:8])
|
||||
|
||||
|
||||
def callsign(msg):
|
||||
"""Aircraft callsign
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
string: callsign
|
||||
"""
|
||||
|
||||
if common.typecode(msg) < 1 or common.typecode(msg) > 4:
|
||||
raise RuntimeError("%s: Not a identification message" % msg)
|
||||
|
||||
chars = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ#####_###############0123456789######'
|
||||
msgbin = common.hex2bin(msg)
|
||||
csbin = msgbin[40:96]
|
||||
|
||||
cs = ''
|
||||
cs += chars[common.bin2int(csbin[0:6])]
|
||||
cs += chars[common.bin2int(csbin[6:12])]
|
||||
cs += chars[common.bin2int(csbin[12:18])]
|
||||
cs += chars[common.bin2int(csbin[18:24])]
|
||||
cs += chars[common.bin2int(csbin[24:30])]
|
||||
cs += chars[common.bin2int(csbin[30:36])]
|
||||
cs += chars[common.bin2int(csbin[36:42])]
|
||||
cs += chars[common.bin2int(csbin[42:48])]
|
||||
|
||||
# clean string, remove spaces and marks, if any.
|
||||
# cs = cs.replace('_', '')
|
||||
cs = cs.replace('#', '')
|
||||
return cs
|
||||
138
pyModeS/decoder/bds/bds09.py
Normal file
138
pyModeS/decoder/bds/bds09.py
Normal file
@@ -0,0 +1,138 @@
|
||||
# Copyright (C) 2018 Junzi Sun (TU Delft)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
# ------------------------------------------
|
||||
# BDS 0,9
|
||||
# ADS-B TC=19
|
||||
# Aircraft Airborn velocity
|
||||
# ------------------------------------------
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
from pyModeS.decoder import common
|
||||
import math
|
||||
|
||||
|
||||
def airborne_velocity(msg, rtn_sources=False):
|
||||
"""Calculate the speed, track (or heading), and vertical rate
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
rtn_source (boolean): If the function will return
|
||||
the sources for direction of travel and vertical
|
||||
rate. This will change the return value from a four
|
||||
element array to a six element array.
|
||||
|
||||
Returns:
|
||||
(int, float, int, string, string, string): speed (kt),
|
||||
ground track or heading (degree),
|
||||
rate of climb/descent (ft/min), speed type
|
||||
('GS' for ground speed, 'AS' for airspeed),
|
||||
direction source ('true_north' for ground track / true north
|
||||
as refrence, 'mag_north' for magnetic north as reference),
|
||||
rate of climb/descent source ('Baro' for barometer, 'GNSS'
|
||||
for GNSS constellation).
|
||||
"""
|
||||
|
||||
if common.typecode(msg) != 19:
|
||||
raise RuntimeError("%s: Not a airborne velocity message, expecting TC=19" % msg)
|
||||
|
||||
mb = common.hex2bin(msg)[32:]
|
||||
|
||||
subtype = common.bin2int(mb[5:8])
|
||||
|
||||
if common.bin2int(mb[14:24]) == 0 or common.bin2int(mb[25:35]) == 0:
|
||||
return None
|
||||
|
||||
if subtype in (1, 2):
|
||||
v_ew_sign = -1 if mb[13]=='1' else 1
|
||||
v_ew = common.bin2int(mb[14:24]) - 1 # east-west velocity
|
||||
if subtype == 2: # Supersonic
|
||||
v_ew *= 4
|
||||
|
||||
v_ns_sign = -1 if mb[24]=='1' else 1
|
||||
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_sn = v_ns_sign * v_ns
|
||||
|
||||
spd = math.sqrt(v_sn*v_sn + v_we*v_we) # unit in kts
|
||||
spd = int(spd)
|
||||
|
||||
trk = math.atan2(v_we, v_sn)
|
||||
trk = math.degrees(trk) # convert to degrees
|
||||
trk = trk if trk >= 0 else trk + 360 # no negative val
|
||||
|
||||
tag = 'GS'
|
||||
trk_or_hdg = round(trk, 2)
|
||||
dir_type = 'true_north'
|
||||
|
||||
else:
|
||||
if mb[13] == '0':
|
||||
hdg = None
|
||||
else:
|
||||
hdg = common.bin2int(mb[14:24]) / 1024.0 * 360.0
|
||||
hdg = round(hdg, 2)
|
||||
|
||||
trk_or_hdg = hdg
|
||||
|
||||
spd = common.bin2int(mb[25:35])
|
||||
spd = None if spd==0 else spd-1
|
||||
if subtype == 4: # Supersonic
|
||||
spd *= 4
|
||||
|
||||
if mb[24]=='0':
|
||||
tag = 'IAS'
|
||||
else:
|
||||
tag = 'TAS'
|
||||
|
||||
dir_type = 'mag_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])
|
||||
rocd = None if vr==0 else int(vr_sign*(vr-1)*64)
|
||||
|
||||
if rtn_sources:
|
||||
return spd, trk_or_hdg, rocd, tag, dir_type, vr_source
|
||||
else:
|
||||
return spd, trk_or_hdg, rocd, tag
|
||||
|
||||
|
||||
def altitude_diff(msg):
|
||||
"""Decode the differece between GNSS and barometric altitude
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string, TC=19
|
||||
|
||||
Returns:
|
||||
int: Altitude difference in ft. Negative value indicates GNSS altitude
|
||||
below barometric altitude.
|
||||
"""
|
||||
tc = common.typecode(msg)
|
||||
|
||||
if tc != 19:
|
||||
raise RuntimeError("%s: Not a airborne velocity message, expecting TC=19" % msg)
|
||||
|
||||
msgbin = common.hex2bin(msg)
|
||||
sign = -1 if int(msgbin[80]) else 1
|
||||
value = common.bin2int(msgbin[81:88])
|
||||
|
||||
if value == 0 or value == 127:
|
||||
return None
|
||||
else:
|
||||
return sign * (value - 1) * 25 # in ft.
|
||||
66
pyModeS/decoder/bds/bds10.py
Normal file
66
pyModeS/decoder/bds/bds10.py
Normal file
@@ -0,0 +1,66 @@
|
||||
# Copyright (C) 2018 Junzi Sun (TU Delft)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# ------------------------------------------
|
||||
# BDS 1,0
|
||||
# Data link capability report
|
||||
# ------------------------------------------
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros
|
||||
|
||||
def is10(msg):
|
||||
"""Check if a message is likely to be BDS code 1,0
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
bool: True or False
|
||||
"""
|
||||
|
||||
if allzeros(msg):
|
||||
return False
|
||||
|
||||
d = hex2bin(data(msg))
|
||||
|
||||
# first 8 bits must be 0x10
|
||||
if d[0:8] != '00010000':
|
||||
return False
|
||||
|
||||
# bit 10 to 14 are reserved
|
||||
if bin2int(d[9:14]) != 0:
|
||||
return False
|
||||
|
||||
# overlay capabilty conflict
|
||||
if d[14] == '1' and bin2int(d[16:23]) < 5:
|
||||
return False
|
||||
if d[14] == '0' and bin2int(d[16:23]) > 4:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def ovc10(msg):
|
||||
"""Return the overlay control capability
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
int: Whether the transponder is OVC capable
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
|
||||
return int(d[14])
|
||||
75
pyModeS/decoder/bds/bds17.py
Normal file
75
pyModeS/decoder/bds/bds17.py
Normal file
@@ -0,0 +1,75 @@
|
||||
# Copyright (C) 2018 Junzi Sun (TU Delft)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
# ------------------------------------------
|
||||
# BDS 1,7
|
||||
# Common usage GICB capability report
|
||||
# ------------------------------------------
|
||||
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros
|
||||
|
||||
|
||||
def is17(msg):
|
||||
"""Check if a message is likely to be BDS code 1,7
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
bool: True or False
|
||||
"""
|
||||
|
||||
if allzeros(msg):
|
||||
return False
|
||||
|
||||
d = hex2bin(data(msg))
|
||||
|
||||
if bin2int(d[28:56]) != 0:
|
||||
return False
|
||||
|
||||
caps = cap17(msg)
|
||||
|
||||
# basic BDS codes for ADS-B shall be supported
|
||||
# assuming ADS-B out is installed (2017EU/2020US mandate)
|
||||
# if not set(['BDS05', 'BDS06', 'BDS08', 'BDS09', 'BDS20']).issubset(caps):
|
||||
# return False
|
||||
|
||||
# at least you can respond who you are
|
||||
if 'BDS20' not in caps:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def cap17(msg):
|
||||
"""Extract capacities from BDS 1,7 message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
list: list of suport BDS codes
|
||||
"""
|
||||
allbds = ['05', '06', '07', '08', '09', '0A', '20', '21', '40', '41',
|
||||
'42', '43', '44', '45', '48', '50', '51', '52', '53', '54',
|
||||
'55', '56', '5F', '60', 'NA', 'NA', 'E1', 'E2']
|
||||
|
||||
d = hex2bin(data(msg))
|
||||
idx = [i for i, v in enumerate(d[:28]) if v=='1']
|
||||
capacity = ['BDS'+allbds[i] for i in idx if allbds[i] is not 'NA']
|
||||
|
||||
return capacity
|
||||
73
pyModeS/decoder/bds/bds20.py
Normal file
73
pyModeS/decoder/bds/bds20.py
Normal file
@@ -0,0 +1,73 @@
|
||||
# Copyright (C) 2018 Junzi Sun (TU Delft)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# ------------------------------------------
|
||||
# BDS 2,0
|
||||
# Aircraft identification
|
||||
# ------------------------------------------
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros
|
||||
|
||||
def is20(msg):
|
||||
"""Check if a message is likely to be BDS code 2,0
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
bool: True or False
|
||||
"""
|
||||
|
||||
if allzeros(msg):
|
||||
return False
|
||||
|
||||
d = hex2bin(data(msg))
|
||||
|
||||
if d[0:8] != '00100000':
|
||||
return False
|
||||
|
||||
cs = cs20(msg)
|
||||
|
||||
if '#' in cs:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def cs20(msg):
|
||||
"""Aircraft callsign
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS40) string
|
||||
|
||||
Returns:
|
||||
string: callsign, max. 8 chars
|
||||
"""
|
||||
chars = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ#####_###############0123456789######'
|
||||
|
||||
d = hex2bin(data(msg))
|
||||
|
||||
cs = ''
|
||||
cs += chars[bin2int(d[8:14])]
|
||||
cs += chars[bin2int(d[14:20])]
|
||||
cs += chars[bin2int(d[20:26])]
|
||||
cs += chars[bin2int(d[26:32])]
|
||||
cs += chars[bin2int(d[32:38])]
|
||||
cs += chars[bin2int(d[38:44])]
|
||||
cs += chars[bin2int(d[44:50])]
|
||||
cs += chars[bin2int(d[50:56])]
|
||||
|
||||
return cs
|
||||
50
pyModeS/decoder/bds/bds30.py
Normal file
50
pyModeS/decoder/bds/bds30.py
Normal file
@@ -0,0 +1,50 @@
|
||||
# Copyright (C) 2018 Junzi Sun (TU Delft)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# ------------------------------------------
|
||||
# BDS 3,0
|
||||
# ACAS active resolution advisory
|
||||
# ------------------------------------------
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros
|
||||
|
||||
def is30(msg):
|
||||
"""Check if a message is likely to be BDS code 2,0
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
bool: True or False
|
||||
"""
|
||||
|
||||
if allzeros(msg):
|
||||
return False
|
||||
|
||||
d = hex2bin(data(msg))
|
||||
|
||||
if d[0:8] != '00110000':
|
||||
return False
|
||||
|
||||
# threat type 3 not assigned
|
||||
if d[28:30] == '11':
|
||||
return False
|
||||
|
||||
# reserved for ACAS III, in far future
|
||||
if bin2int(d[15:22]) >= 48:
|
||||
return False
|
||||
|
||||
return True
|
||||
138
pyModeS/decoder/bds/bds40.py
Normal file
138
pyModeS/decoder/bds/bds40.py
Normal file
@@ -0,0 +1,138 @@
|
||||
# Copyright (C) 2018 Junzi Sun (TU Delft)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# ------------------------------------------
|
||||
# BDS 4,0
|
||||
# Selected vertical intention
|
||||
# ------------------------------------------
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
import warnings
|
||||
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros, wrongstatus
|
||||
|
||||
|
||||
def is40(msg):
|
||||
"""Check if a message is likely to be BDS code 4,0
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
bool: True or False
|
||||
"""
|
||||
|
||||
if allzeros(msg):
|
||||
return False
|
||||
|
||||
d = hex2bin(data(msg))
|
||||
|
||||
# status bit 1, 14, and 27
|
||||
|
||||
if wrongstatus(d, 1, 2, 13):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 14, 15, 26):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 27, 28, 39):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 48, 49, 51):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 54, 55, 56):
|
||||
return False
|
||||
|
||||
# bits 40-47 and 52-53 shall all be zero
|
||||
|
||||
if bin2int(d[39:47]) != 0:
|
||||
return False
|
||||
|
||||
if bin2int(d[51:53]) != 0:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def selalt40mcp(msg):
|
||||
"""Selected altitude, MCP/FCU
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS40) string
|
||||
|
||||
Returns:
|
||||
int: altitude in feet
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
|
||||
if d[0] == "0":
|
||||
return None
|
||||
|
||||
alt = bin2int(d[1:13]) * 16 # ft
|
||||
return alt
|
||||
|
||||
|
||||
def selalt40fms(msg):
|
||||
"""Selected altitude, FMS
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS40) string
|
||||
|
||||
Returns:
|
||||
int: altitude in feet
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
|
||||
if d[13] == "0":
|
||||
return None
|
||||
|
||||
alt = bin2int(d[14:26]) * 16 # ft
|
||||
return alt
|
||||
|
||||
|
||||
def p40baro(msg):
|
||||
"""Barometric pressure setting
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS40) string
|
||||
|
||||
Returns:
|
||||
float: pressure in millibar
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
|
||||
if d[26] == "0":
|
||||
return None
|
||||
|
||||
p = bin2int(d[27:39]) * 0.1 + 800 # millibar
|
||||
return p
|
||||
|
||||
|
||||
def alt40mcp(msg):
|
||||
warnings.simplefilter("once", DeprecationWarning)
|
||||
warnings.warn(
|
||||
"alt40mcp() has been renamed to selalt40mcp(). It will be removed in the future.",
|
||||
DeprecationWarning,
|
||||
)
|
||||
return selalt40mcp(msg)
|
||||
|
||||
|
||||
def alt40fms(msg):
|
||||
warnings.simplefilter("once", DeprecationWarning)
|
||||
warnings.warn(
|
||||
"alt40fms() has been renamed to selalt40fms(). It will be removed in the future.",
|
||||
DeprecationWarning,
|
||||
)
|
||||
return selalt40mcp(msg)
|
||||
183
pyModeS/decoder/bds/bds44.py
Normal file
183
pyModeS/decoder/bds/bds44.py
Normal file
@@ -0,0 +1,183 @@
|
||||
# Copyright (C) 2018 Junzi Sun (TU Delft)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# ------------------------------------------
|
||||
# BDS 4,4
|
||||
# Meteorological routine air report
|
||||
# ------------------------------------------
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
from pyModeS.decoder.common import (
|
||||
hex2bin,
|
||||
bin2int,
|
||||
data,
|
||||
allzeros,
|
||||
wrongstatus,
|
||||
)
|
||||
|
||||
|
||||
def is44(msg):
|
||||
"""Check if a message is likely to be BDS code 4,4.
|
||||
|
||||
Meteorological routine air report
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
bool: True or False
|
||||
|
||||
"""
|
||||
if allzeros(msg):
|
||||
return False
|
||||
|
||||
d = hex2bin(data(msg))
|
||||
|
||||
# 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
|
||||
|
||||
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 True
|
||||
|
||||
|
||||
def wind44(msg):
|
||||
"""Wind speed and direction.
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
(int, float): speed (kt), direction (degree)
|
||||
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
|
||||
status = int(d[4])
|
||||
if not status:
|
||||
return None, None
|
||||
|
||||
speed = bin2int(d[5:14]) # knots
|
||||
direction = bin2int(d[14:23]) * 180.0 / 256.0 # degree
|
||||
|
||||
return round(speed, 0), round(direction, 1)
|
||||
|
||||
|
||||
def temp44(msg):
|
||||
"""Static air temperature.
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
float, float: temperature and alternative temperature in Celsius degree.
|
||||
Note: Two values returns due to what seems to be an inconsistancy
|
||||
error in ICAO 9871 (2008) Appendix A-67.
|
||||
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
|
||||
sign = int(d[23])
|
||||
value = bin2int(d[24:34])
|
||||
|
||||
if sign:
|
||||
value = value - 1024
|
||||
|
||||
temp = value * 0.25 # celsius
|
||||
temp = round(temp, 2)
|
||||
|
||||
temp_alternative = value * 0.125 # celsius
|
||||
temp_alternative = round(temp_alternative, 3)
|
||||
|
||||
return temp, temp_alternative
|
||||
|
||||
|
||||
def p44(msg):
|
||||
"""Static pressure.
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
int: static pressure in hPa
|
||||
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
|
||||
if d[34] == "0":
|
||||
return None
|
||||
|
||||
p = bin2int(d[35:46]) # hPa
|
||||
|
||||
return p
|
||||
|
||||
|
||||
def hum44(msg):
|
||||
"""humidity
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
float: percentage of humidity, [0 - 100] %
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
|
||||
if d[49] == "0":
|
||||
return None
|
||||
|
||||
hm = bin2int(d[50:56]) * 100.0 / 64 # %
|
||||
|
||||
return round(hm, 1)
|
||||
|
||||
|
||||
def turb44(msg):
|
||||
"""Turblence.
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
int: turbulence level. 0=NIL, 1=Light, 2=Moderate, 3=Severe
|
||||
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
|
||||
if d[46] == "0":
|
||||
return None
|
||||
|
||||
turb = bin2int(d[47:49])
|
||||
|
||||
return turb
|
||||
224
pyModeS/decoder/bds/bds45.py
Normal file
224
pyModeS/decoder/bds/bds45.py
Normal file
@@ -0,0 +1,224 @@
|
||||
# Copyright (C) 2018 Junzi Sun (TU Delft)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# ------------------------------------------
|
||||
# BDS 4,5
|
||||
# Meteorological hazard report
|
||||
# ------------------------------------------
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros, wrongstatus
|
||||
|
||||
|
||||
def is45(msg):
|
||||
"""Check if a message is likely to be BDS code 4,5.
|
||||
|
||||
Meteorological hazard report
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
bool: True or False
|
||||
|
||||
"""
|
||||
if allzeros(msg):
|
||||
return False
|
||||
|
||||
d = hex2bin(data(msg))
|
||||
|
||||
# status bit 1, 4, 7, 10, 13, 16, 27, 39
|
||||
if wrongstatus(d, 1, 2, 3):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 4, 5, 6):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 7, 8, 9):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 10, 11, 12):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 13, 14, 15):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 16, 17, 26):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 27, 28, 38):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 39, 40, 51):
|
||||
return False
|
||||
|
||||
# reserved
|
||||
if 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 (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
int: Turbulence level. 0=NIL, 1=Light, 2=Moderate, 3=Severe
|
||||
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
if d[0] == '0':
|
||||
return None
|
||||
|
||||
turb = bin2int(d[1:3])
|
||||
return turb
|
||||
|
||||
|
||||
def ws45(msg):
|
||||
"""Wind shear.
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
int: Wind shear level. 0=NIL, 1=Light, 2=Moderate, 3=Severe
|
||||
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
if d[3] == '0':
|
||||
return None
|
||||
|
||||
ws = bin2int(d[4:6])
|
||||
return ws
|
||||
|
||||
|
||||
def mb45(msg):
|
||||
"""Microburst.
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
int: Microburst level. 0=NIL, 1=Light, 2=Moderate, 3=Severe
|
||||
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
if d[6] == '0':
|
||||
return None
|
||||
|
||||
mb = bin2int(d[7:9])
|
||||
return mb
|
||||
|
||||
|
||||
def ic45(msg):
|
||||
"""Icing.
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
int: Icing level. 0=NIL, 1=Light, 2=Moderate, 3=Severe
|
||||
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
if d[9] == '0':
|
||||
return None
|
||||
|
||||
ic = bin2int(d[10:12])
|
||||
return ic
|
||||
|
||||
|
||||
def wv45(msg):
|
||||
"""Wake vortex.
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
int: Wake vortex level. 0=NIL, 1=Light, 2=Moderate, 3=Severe
|
||||
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
if d[12] == '0':
|
||||
return None
|
||||
|
||||
ws = bin2int(d[13:15])
|
||||
return ws
|
||||
|
||||
|
||||
def temp45(msg):
|
||||
"""Static air temperature.
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
float: tmeperature in Celsius degree
|
||||
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
|
||||
sign = int(d[16])
|
||||
value = 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 (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
int: static pressure in hPa
|
||||
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
if d[26] == '0':
|
||||
return None
|
||||
p = bin2int(d[27:38]) # hPa
|
||||
return p
|
||||
|
||||
|
||||
def rh45(msg):
|
||||
"""Radio height.
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
int: radio height in ft
|
||||
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
if d[38] == '0':
|
||||
return None
|
||||
rh = bin2int(d[39:51]) * 16
|
||||
return rh
|
||||
189
pyModeS/decoder/bds/bds50.py
Normal file
189
pyModeS/decoder/bds/bds50.py
Normal file
@@ -0,0 +1,189 @@
|
||||
# Copyright (C) 2018 Junzi Sun (TU Delft)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# ------------------------------------------
|
||||
# BDS 5,0
|
||||
# Track and turn report
|
||||
# ------------------------------------------
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros, wrongstatus
|
||||
|
||||
|
||||
def is50(msg):
|
||||
"""Check if a message is likely to be BDS code 5,0
|
||||
(Track and turn report)
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
bool: True or False
|
||||
"""
|
||||
|
||||
if allzeros(msg):
|
||||
return False
|
||||
|
||||
d = hex2bin(data(msg))
|
||||
|
||||
# status bit 1, 12, 24, 35, 46
|
||||
|
||||
if wrongstatus(d, 1, 3, 11):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 12, 13, 23):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 24, 25, 34):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 35, 36, 45):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 46, 47, 56):
|
||||
return False
|
||||
|
||||
roll = roll50(msg)
|
||||
if (roll is not None) and abs(roll) > 60:
|
||||
return False
|
||||
|
||||
gs = gs50(msg)
|
||||
if gs is not None and gs > 600:
|
||||
return False
|
||||
|
||||
tas = tas50(msg)
|
||||
if tas is not None and tas > 500:
|
||||
return False
|
||||
|
||||
if (gs is not None) and (tas is not None) and (abs(tas - gs) > 200):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def roll50(msg):
|
||||
"""Roll angle, BDS 5,0 message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS50) string
|
||||
|
||||
Returns:
|
||||
float: angle in degrees,
|
||||
negative->left wing down, positive->right wing down
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
|
||||
if d[0] == '0':
|
||||
return None
|
||||
|
||||
sign = int(d[1]) # 1 -> left wing down
|
||||
value = bin2int(d[2:11])
|
||||
|
||||
if sign:
|
||||
value = value - 512
|
||||
|
||||
angle = value * 45.0 / 256.0 # degree
|
||||
return round(angle, 1)
|
||||
|
||||
|
||||
def trk50(msg):
|
||||
"""True track angle, BDS 5,0 message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS50) string
|
||||
|
||||
Returns:
|
||||
float: angle in degrees to true north (from 0 to 360)
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
|
||||
if d[11] == '0':
|
||||
return None
|
||||
|
||||
sign = int(d[12]) # 1 -> west
|
||||
value = bin2int(d[13:23])
|
||||
|
||||
if sign:
|
||||
value = value - 1024
|
||||
|
||||
trk = value * 90.0 / 512.0
|
||||
|
||||
# convert from [-180, 180] to [0, 360]
|
||||
if trk < 0:
|
||||
trk = 360 + trk
|
||||
|
||||
return round(trk, 3)
|
||||
|
||||
|
||||
def gs50(msg):
|
||||
"""Ground speed, BDS 5,0 message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS50) string
|
||||
|
||||
Returns:
|
||||
int: ground speed in knots
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
|
||||
if d[23] == '0':
|
||||
return None
|
||||
|
||||
spd = bin2int(d[24:34]) * 2 # kts
|
||||
return spd
|
||||
|
||||
|
||||
def rtrk50(msg):
|
||||
"""Track angle rate, BDS 5,0 message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS50) string
|
||||
|
||||
Returns:
|
||||
float: angle rate in degrees/second
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
|
||||
if d[34] == '0':
|
||||
return None
|
||||
|
||||
if d[36:45] == "111111111":
|
||||
return None
|
||||
|
||||
sign = int(d[35]) # 1 -> negative value, two's complement
|
||||
value = bin2int(d[36:45])
|
||||
if sign:
|
||||
value = value - 512
|
||||
|
||||
angle = value * 8.0 / 256.0 # degree / sec
|
||||
return round(angle, 3)
|
||||
|
||||
|
||||
def tas50(msg):
|
||||
"""Aircraft true airspeed, BDS 5,0 message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS50) string
|
||||
|
||||
Returns:
|
||||
int: true airspeed in knots
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
|
||||
if d[45] == '0':
|
||||
return None
|
||||
|
||||
tas = bin2int(d[46:56]) * 2 # kts
|
||||
return tas
|
||||
182
pyModeS/decoder/bds/bds53.py
Normal file
182
pyModeS/decoder/bds/bds53.py
Normal file
@@ -0,0 +1,182 @@
|
||||
# Copyright (C) 2018 Junzi Sun (TU Delft)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# ------------------------------------------
|
||||
# BDS 5,3
|
||||
# Air-referenced state vector
|
||||
# ------------------------------------------
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros, wrongstatus
|
||||
|
||||
|
||||
def is53(msg):
|
||||
"""Check if a message is likely to be BDS code 5,3
|
||||
(Air-referenced state vector)
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
bool: True or False
|
||||
"""
|
||||
|
||||
if allzeros(msg):
|
||||
return False
|
||||
|
||||
d = hex2bin(data(msg))
|
||||
|
||||
# status bit 1, 13, 24, 34, 47
|
||||
|
||||
if wrongstatus(d, 1, 3, 12):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 13, 14, 23):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 24, 25, 33):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 34, 35, 46):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 47, 49, 56):
|
||||
return False
|
||||
|
||||
ias = ias53(msg)
|
||||
if ias is not None and ias > 500:
|
||||
return False
|
||||
|
||||
mach = mach53(msg)
|
||||
if mach is not None and mach > 1:
|
||||
return False
|
||||
|
||||
tas = tas53(msg)
|
||||
if tas is not None and tas > 500:
|
||||
return False
|
||||
|
||||
vr = vr53(msg)
|
||||
if vr is not None and abs(vr) > 8000:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def hdg53(msg):
|
||||
"""Magnetic heading, BDS 5,3 message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS53) string
|
||||
|
||||
Returns:
|
||||
float: angle in degrees to true north (from 0 to 360)
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
|
||||
if d[0] == '0':
|
||||
return None
|
||||
|
||||
sign = int(d[1]) # 1 -> west
|
||||
value = bin2int(d[2:12])
|
||||
|
||||
if sign:
|
||||
value = value - 1024
|
||||
|
||||
hdg = value * 90.0 / 512.0 # degree
|
||||
|
||||
# convert from [-180, 180] to [0, 360]
|
||||
if hdg < 0:
|
||||
hdg = 360 + hdg
|
||||
|
||||
return round(hdg, 3)
|
||||
|
||||
|
||||
def ias53(msg):
|
||||
"""Indicated airspeed, DBS 5,3 message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message
|
||||
|
||||
Returns:
|
||||
int: indicated arispeed in knots
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
|
||||
if d[12] == '0':
|
||||
return None
|
||||
|
||||
ias = bin2int(d[13:23]) # knots
|
||||
return ias
|
||||
|
||||
|
||||
def mach53(msg):
|
||||
"""MACH number, DBS 5,3 message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message
|
||||
|
||||
Returns:
|
||||
float: MACH number
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
|
||||
if d[23] == '0':
|
||||
return None
|
||||
|
||||
mach = bin2int(d[24:33]) * 0.008
|
||||
return round(mach, 3)
|
||||
|
||||
|
||||
def tas53(msg):
|
||||
"""Aircraft true airspeed, BDS 5,3 message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message
|
||||
|
||||
Returns:
|
||||
float: true airspeed in knots
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
|
||||
if d[33] == '0':
|
||||
return None
|
||||
|
||||
tas = bin2int(d[34:46]) * 0.5 # kts
|
||||
return round(tas, 1)
|
||||
|
||||
def vr53(msg):
|
||||
"""Vertical rate
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS60) string
|
||||
|
||||
Returns:
|
||||
int: vertical rate in feet/minutes
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
|
||||
if d[46] == '0':
|
||||
return None
|
||||
|
||||
sign = int(d[47]) # 1 -> negative value, two's complement
|
||||
value = bin2int(d[48:56])
|
||||
|
||||
if value == 0 or value == 255: # all zeros or all ones
|
||||
return 0
|
||||
|
||||
value = value - 256 if sign else value
|
||||
roc = value * 64 # feet/min
|
||||
|
||||
return roc
|
||||
189
pyModeS/decoder/bds/bds60.py
Normal file
189
pyModeS/decoder/bds/bds60.py
Normal file
@@ -0,0 +1,189 @@
|
||||
# Copyright (C) 2018 Junzi Sun (TU Delft)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# ------------------------------------------
|
||||
# BDS 6,0
|
||||
# Heading and speed report
|
||||
# ------------------------------------------
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros, wrongstatus
|
||||
|
||||
def is60(msg):
|
||||
"""Check if a message is likely to be BDS code 6,0
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
bool: True or False
|
||||
"""
|
||||
|
||||
if allzeros(msg):
|
||||
return False
|
||||
|
||||
d = hex2bin(data(msg))
|
||||
|
||||
# status bit 1, 13, 24, 35, 46
|
||||
|
||||
if wrongstatus(d, 1, 2, 12):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 13, 14, 23):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 24, 25, 34):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 35, 36, 45):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 46, 47, 56):
|
||||
return False
|
||||
|
||||
ias = ias60(msg)
|
||||
if ias is not None and ias > 500:
|
||||
return False
|
||||
|
||||
mach = mach60(msg)
|
||||
if mach is not None and mach > 1:
|
||||
return False
|
||||
|
||||
vr_baro = vr60baro(msg)
|
||||
if vr_baro is not None and abs(vr_baro) > 6000:
|
||||
return False
|
||||
|
||||
vr_ins = vr60ins(msg)
|
||||
if vr_ins is not None and abs(vr_ins) > 6000:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def hdg60(msg):
|
||||
"""Megnetic heading of aircraft
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS60) string
|
||||
|
||||
Returns:
|
||||
float: heading in degrees to megnetic north (from 0 to 360)
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
|
||||
if d[0] == '0':
|
||||
return None
|
||||
|
||||
sign = int(d[1]) # 1 -> west
|
||||
value = bin2int(d[2:12])
|
||||
|
||||
if sign:
|
||||
value = value - 1024
|
||||
|
||||
hdg = value * 90 / 512.0 # degree
|
||||
|
||||
# convert from [-180, 180] to [0, 360]
|
||||
if hdg < 0:
|
||||
hdg = 360 + hdg
|
||||
|
||||
return round(hdg, 3)
|
||||
|
||||
|
||||
def ias60(msg):
|
||||
"""Indicated airspeed
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS60) string
|
||||
|
||||
Returns:
|
||||
int: indicated airspeed in knots
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
|
||||
if d[12] == '0':
|
||||
return None
|
||||
|
||||
ias = bin2int(d[13:23]) # kts
|
||||
return ias
|
||||
|
||||
|
||||
def mach60(msg):
|
||||
"""Aircraft MACH number
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS60) string
|
||||
|
||||
Returns:
|
||||
float: MACH number
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
|
||||
if d[23] == '0':
|
||||
return None
|
||||
|
||||
mach = bin2int(d[24:34]) * 2.048 / 512.0
|
||||
return round(mach, 3)
|
||||
|
||||
|
||||
def vr60baro(msg):
|
||||
"""Vertical rate from barometric measurement, this value may be very noisy.
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS60) string
|
||||
|
||||
Returns:
|
||||
int: vertical rate in feet/minutes
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
|
||||
if d[34] == '0':
|
||||
return None
|
||||
|
||||
sign = int(d[35]) # 1 -> negative value, two's complement
|
||||
value = bin2int(d[36:45])
|
||||
|
||||
if value == 0 or value == 511: # all zeros or all ones
|
||||
return 0
|
||||
|
||||
value = value - 512 if sign else value
|
||||
|
||||
roc = value * 32 # feet/min
|
||||
return roc
|
||||
|
||||
|
||||
def vr60ins(msg):
|
||||
"""Vertical rate messured by onbard equiments (IRS, AHRS)
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS60) string
|
||||
|
||||
Returns:
|
||||
int: vertical rate in feet/minutes
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
|
||||
if d[45] == '0':
|
||||
return None
|
||||
|
||||
sign = int(d[46]) # 1 -> negative value, two's complement
|
||||
value = bin2int(d[47:56])
|
||||
|
||||
if value == 0 or value == 511: # all zeros or all ones
|
||||
return 0
|
||||
|
||||
value = value - 512 if sign else value
|
||||
|
||||
roc = value * 32 # feet/min
|
||||
return roc
|
||||
37
pyModeS/decoder/commb.py
Normal file
37
pyModeS/decoder/commb.py
Normal file
@@ -0,0 +1,37 @@
|
||||
"""Comm-B Wrapper.
|
||||
|
||||
The Comm-B wrapper 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
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
|
||||
# ELS - elementary surveillance
|
||||
from pyModeS.decoder.bds.bds10 import *
|
||||
from pyModeS.decoder.bds.bds17 import *
|
||||
from pyModeS.decoder.bds.bds20 import *
|
||||
from pyModeS.decoder.bds.bds30 import *
|
||||
|
||||
# ELS - enhanced surveillance
|
||||
from pyModeS.decoder.bds.bds40 import *
|
||||
from pyModeS.decoder.bds.bds50 import *
|
||||
from pyModeS.decoder.bds.bds60 import *
|
||||
|
||||
# MRAR and MHR
|
||||
from pyModeS.decoder.bds.bds44 import *
|
||||
from pyModeS.decoder.bds.bds45 import *
|
||||
365
pyModeS/decoder/common.py
Normal file
365
pyModeS/decoder/common.py
Normal file
@@ -0,0 +1,365 @@
|
||||
from __future__ import absolute_import, print_function, division
|
||||
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 int2hex(n):
|
||||
"""Convert a integer to hexadecimal string."""
|
||||
return hex(n)[2:].rjust(6, '0').upper()
|
||||
|
||||
|
||||
def bin2int(binstr):
|
||||
"""Convert a binary string to integer."""
|
||||
return int(binstr, 2)
|
||||
|
||||
|
||||
def bin2hex(hexstr):
|
||||
"""Convert a hexdecimal string to integer."""
|
||||
return int2hex(bin2int(hexstr))
|
||||
|
||||
|
||||
def bin2np(binstr):
|
||||
"""Convert a binary string to numpy array."""
|
||||
return np.array([int(i) for i in binstr])
|
||||
|
||||
|
||||
def np2bin(npbin):
|
||||
"""Convert a binary numpy array to string."""
|
||||
return np.array2string(npbin, separator='')[1:-1]
|
||||
|
||||
|
||||
def df(msg):
|
||||
"""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. 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 = 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 = bin2int(np2bin(msgnpbin[-24:]))
|
||||
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 = 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):
|
||||
"""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
|
||||
29
pyModeS/decoder/ehs.py
Normal file
29
pyModeS/decoder/ehs.py
Normal file
@@ -0,0 +1,29 @@
|
||||
"""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
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
import warnings
|
||||
|
||||
from pyModeS.decoder.bds.bds40 import *
|
||||
from pyModeS.decoder.bds.bds50 import *
|
||||
from pyModeS.decoder.bds.bds60 import *
|
||||
from pyModeS.decoder.bds import infer
|
||||
|
||||
warnings.simplefilter('once', DeprecationWarning)
|
||||
warnings.warn("pms.ehs module is deprecated. Please use pms.commb instead.", DeprecationWarning)
|
||||
|
||||
def BDS(msg):
|
||||
warnings.warn("pms.ehs.BDS() is deprecated, use pms.bds.infer() instead.", DeprecationWarning)
|
||||
return infer(msg)
|
||||
|
||||
def icao(msg):
|
||||
from pyModeS.decoder.common import icao
|
||||
return icao(msg)
|
||||
22
pyModeS/decoder/els.py
Normal file
22
pyModeS/decoder/els.py
Normal file
@@ -0,0 +1,22 @@
|
||||
"""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 __future__ import absolute_import, print_function, division
|
||||
|
||||
from pyModeS.decoder.bds.bds10 import *
|
||||
from pyModeS.decoder.bds.bds17 import *
|
||||
from pyModeS.decoder.bds.bds20 import *
|
||||
from pyModeS.decoder.bds.bds30 import *
|
||||
|
||||
import warnings
|
||||
warnings.simplefilter('once', DeprecationWarning)
|
||||
warnings.warn("pms.els module is deprecated. Please use pms.commb instead.", DeprecationWarning)
|
||||
23
pyModeS/decoder/surv.py
Normal file
23
pyModeS/decoder/surv.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# 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
|
||||
|
||||
[To be implemented]
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
from pyModeS.decoder import common
|
||||
125
pyModeS/decoder/uncertainty.py
Normal file
125
pyModeS/decoder/uncertainty.py
Normal file
@@ -0,0 +1,125 @@
|
||||
"""Uncertainty parameters.
|
||||
|
||||
See source code at: https://github.com/junzis/pyModeS/blob/master/pyModeS/decoder/uncertainty.py
|
||||
"""
|
||||
|
||||
NA = None
|
||||
|
||||
TC_NUCp_lookup = {
|
||||
0:0, 5:9, 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 = {
|
||||
5:11, 6:10, 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 = {
|
||||
5:11, 6:10, 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 = {
|
||||
9: {'HPL':7.5, 'RCu':3, 'RCv':4},
|
||||
8: {'HPL':25, 'RCu':10, 'RCv':15},
|
||||
7: {'HPL':185, 'RCu':93, 'RCv':NA},
|
||||
6: {'HPL':370, 'RCu':185, 'RCv':NA},
|
||||
5: {'HPL':926, 'RCu':463, 'RCv':NA},
|
||||
4: {'HPL':1852, 'RCu':926, 'RCv':NA},
|
||||
3: {'HPL':3704, 'RCu':1852, 'RCv':NA},
|
||||
2: {'HPL':18520, 'RCu':9260, 'RCv':NA},
|
||||
1: {'HPL':37040, 'RCu':18520, 'RCv':NA},
|
||||
0: {'HPL':NA, 'RCu':NA, 'RCv':NA},
|
||||
}
|
||||
|
||||
NUCv = {
|
||||
0: {'HVE':NA, 'VVE':NA},
|
||||
1: {'HVE':10, 'VVE':15.2},
|
||||
2: {'HVE':3, 'VVE':4.5},
|
||||
3: {'HVE':1, 'VVE':1.5},
|
||||
4: {'HVE':0.3, 'VVE':0.46},
|
||||
}
|
||||
|
||||
NACp = {
|
||||
11: {'EPU': 3, 'VEPU': 4},
|
||||
10: {'EPU': 10, 'VEPU': 15},
|
||||
9: {'EPU': 30, 'VEPU': 45},
|
||||
8: {'EPU': 93, 'VEPU': NA},
|
||||
7: {'EPU': 185, 'VEPU': NA},
|
||||
6: {'EPU': 556, 'VEPU': NA},
|
||||
5: {'EPU': 926, 'VEPU': NA},
|
||||
4: {'EPU': 1852, 'VEPU': NA},
|
||||
3: {'EPU': 3704, 'VEPU': NA},
|
||||
2: {'EPU': 7408, 'VEPU': NA},
|
||||
1: {'EPU': 18520, 'VEPU': NA},
|
||||
0: {'EPU': NA, 'VEPU': NA},
|
||||
}
|
||||
|
||||
NACv = {
|
||||
0: {'HFOMr':NA, 'VFOMr':NA},
|
||||
1: {'HFOMr':10, 'VFOMr':15.2},
|
||||
2: {'HFOMr':3, 'VFOMr':4.5},
|
||||
3: {'HFOMr':1, 'VFOMr':1.5},
|
||||
4: {'HFOMr':0.3, 'VFOMr':0.46},
|
||||
}
|
||||
|
||||
SIL = {
|
||||
3: {'PE_RCu': 1e-7, 'PE_VPL': 2e-7},
|
||||
2: {'PE_RCu': 1e-5, 'PE_VPL': 1e-5},
|
||||
1: {'PE_RCu': 1e-3, 'PE_VPL': 1e-3},
|
||||
0: {'PE_RCu': NA, 'PE_VPL': NA},
|
||||
}
|
||||
|
||||
|
||||
NICv1 = {
|
||||
# NIC is used as the index at second Level
|
||||
|
||||
11: {0: {'Rc': 7.5, 'VPL': 11}},
|
||||
10: {0: {'Rc': 25, 'VPL': 37.5}},
|
||||
9: {1: {'Rc': 75, 'VPL': 112}},
|
||||
8: {0: {'Rc': 185, 'VPL': NA}},
|
||||
7: {0: {'Rc': 370, 'VPL': NA}},
|
||||
6: {
|
||||
0: {'Rc': 926, 'VPL': NA},
|
||||
1: {'Rc': 1111, 'VPL': NA},
|
||||
},
|
||||
5: {0: {'Rc': 1852, 'VPL': NA}},
|
||||
4: {0: {'Rc': 3702, '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 = {
|
||||
# Decimal value of [NICa NICb/NICc] is used as the index at second Level
|
||||
|
||||
11: {0: {'Rc': 7.5}},
|
||||
10: {0: {'Rc': 25}},
|
||||
9: {
|
||||
2: {'Rc': 75},
|
||||
3: {'Rc': 75},
|
||||
},
|
||||
8: {0: {'Rc': 185}},
|
||||
7: {
|
||||
0: {'Rc': 370},
|
||||
3: {'Rc': 370},
|
||||
},
|
||||
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}},
|
||||
}
|
||||
985
pyModeS/ehs.py
985
pyModeS/ehs.py
@@ -1,985 +0,0 @@
|
||||
# Copyright (C) 2016 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/>.
|
||||
|
||||
"""
|
||||
A python package for decoding ModeS (DF20, DF21) messages.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
from . import util, modes_common
|
||||
|
||||
def icao(msg):
|
||||
return modes_common.icao(msg)
|
||||
|
||||
def data(msg):
|
||||
"""Return the data frame in the message, bytes 9 to 22"""
|
||||
return msg[8:22]
|
||||
|
||||
def isnull(msg):
|
||||
"""check if the data bits are all zeros
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
bool: True or False
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
if util.bin2int(d) > 0:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def checkbits(data, sb, msb, lsb):
|
||||
"""Check if the status bit and field bits are consistency. This Function
|
||||
is used for checking BDS code versions.
|
||||
"""
|
||||
|
||||
# status bit, most significant bit, least significant bit
|
||||
status = int(data[sb-1])
|
||||
value = util.bin2int(data[msb-1:lsb])
|
||||
|
||||
if not status:
|
||||
if value != 0:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
# ------------------------------------------
|
||||
# Common functions
|
||||
# ------------------------------------------
|
||||
|
||||
def df20alt(msg):
|
||||
"""Computes the altitude from DF20 message, bit 20-32
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
int: altitude in ft
|
||||
"""
|
||||
|
||||
if util.df(msg) != 20:
|
||||
raise RuntimeError("Message must be Downlink Format 20.")
|
||||
|
||||
return modes_common.altcode(msg)
|
||||
|
||||
|
||||
def df21id(msg):
|
||||
"""Computes identity (squawk code) from DF21, bit 20-32
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
string: squawk code
|
||||
"""
|
||||
|
||||
if util.df(msg) != 21:
|
||||
raise RuntimeError("Message must be Downlink Format 21.")
|
||||
|
||||
return modes_common.idcode(msg)
|
||||
|
||||
# ------------------------------------------
|
||||
# BDS 1,7
|
||||
# Common usage GICB capability report
|
||||
# ------------------------------------------
|
||||
|
||||
def isBDS17(msg):
|
||||
"""Check if a message is likely to be BDS code 1,7
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
bool: True or False
|
||||
"""
|
||||
|
||||
if isnull(msg):
|
||||
return False
|
||||
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
result = True
|
||||
|
||||
if util.bin2int(d[28:56]) != 0:
|
||||
result &= False
|
||||
|
||||
caps = cap17(msg)
|
||||
|
||||
# basic BDS codes for ADS-B shall be supported
|
||||
# assuming ADS-B out is installed (2017EU/2020US mandate)
|
||||
if not set(['BDS05', 'BDS06', 'BDS09', 'BDS20']).issubset(caps):
|
||||
result &= False
|
||||
|
||||
return result
|
||||
|
||||
def cap17(msg):
|
||||
"""Extract capacities from BDS 1,7 message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
list: list of suport BDS codes
|
||||
"""
|
||||
allbds = ['05', '06', '07', '08', '09', '0A', '20', '21', '40', '41',
|
||||
'42', '43', '44', '45', '48', '50', '51', '52', '53', '54',
|
||||
'55', '56', '5F', '60', 'NA', 'NA', 'E1', 'E2']
|
||||
|
||||
d = util.hex2bin(data(msg))
|
||||
idx = [i for i, v in enumerate(d[:28]) if v=='1']
|
||||
capacity = ['BDS'+allbds[i] for i in idx if allbds[i] is not 'NA']
|
||||
|
||||
return capacity
|
||||
|
||||
# ------------------------------------------
|
||||
# BDS 2,0
|
||||
# Aircraft identification
|
||||
# ------------------------------------------
|
||||
|
||||
def isBDS20(msg):
|
||||
"""Check if a message is likely to be BDS code 2,0
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
bool: True or False
|
||||
"""
|
||||
|
||||
if isnull(msg):
|
||||
return False
|
||||
|
||||
# status bit 1, 14, and 27
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
result = True
|
||||
|
||||
if util.bin2int(d[0:4]) != 2 or util.bin2int(d[4:8]) != 0:
|
||||
result &= False
|
||||
|
||||
cs = callsign(msg)
|
||||
|
||||
if '#' in cs:
|
||||
result &= False
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def callsign(msg):
|
||||
"""Aircraft callsign
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS40) string
|
||||
|
||||
Returns:
|
||||
string: callsign, max. 8 chars
|
||||
"""
|
||||
chars = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ#####_###############0123456789######'
|
||||
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
cs = ''
|
||||
cs += chars[util.bin2int(d[8:14])]
|
||||
cs += chars[util.bin2int(d[14:20])]
|
||||
cs += chars[util.bin2int(d[20:26])]
|
||||
cs += chars[util.bin2int(d[26:32])]
|
||||
cs += chars[util.bin2int(d[32:38])]
|
||||
cs += chars[util.bin2int(d[38:44])]
|
||||
cs += chars[util.bin2int(d[44:50])]
|
||||
cs += chars[util.bin2int(d[50:56])]
|
||||
|
||||
return cs
|
||||
|
||||
|
||||
# ------------------------------------------
|
||||
# BDS 4,0
|
||||
# Selected vertical intention
|
||||
# ------------------------------------------
|
||||
|
||||
def isBDS40(msg):
|
||||
"""Check if a message is likely to be BDS code 4,0
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
bool: True or False
|
||||
"""
|
||||
|
||||
if isnull(msg):
|
||||
return False
|
||||
|
||||
# status bit 1, 14, and 27
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
result = True
|
||||
|
||||
result = result & checkbits(d, 1, 2, 13) \
|
||||
& checkbits(d, 14, 15, 26) & checkbits(d, 27, 28, 39)
|
||||
|
||||
# bits 40-47 and 52-53 shall all be zero
|
||||
if util.bin2int(d[39:47]) != 0:
|
||||
result &= False
|
||||
|
||||
if util.bin2int(d[51:53]) != 0:
|
||||
result &= False
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def alt40mcp(msg):
|
||||
"""Selected altitude, MCP/FCU
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS40) string
|
||||
|
||||
Returns:
|
||||
int: altitude in feet
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
if d[0] == '0':
|
||||
return None
|
||||
|
||||
alt = util.bin2int(d[1:13]) * 16 # ft
|
||||
return alt
|
||||
|
||||
|
||||
def alt40fms(msg):
|
||||
"""Selected altitude, FMS
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS40) string
|
||||
|
||||
Returns:
|
||||
int: altitude in feet
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
if d[13] == '0':
|
||||
return None
|
||||
|
||||
alt = util.bin2int(d[14:26]) * 16 # ft
|
||||
return alt
|
||||
|
||||
|
||||
def p40baro(msg):
|
||||
"""Barometric pressure setting
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS40) string
|
||||
|
||||
Returns:
|
||||
float: pressure in millibar
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
if d[26] == '0':
|
||||
return None
|
||||
|
||||
p = util.bin2int(d[27:39]) * 0.1 + 800 # millibar
|
||||
return p
|
||||
|
||||
|
||||
# ------------------------------------------
|
||||
# BDS 4,4
|
||||
# Meteorological routine air report
|
||||
# ------------------------------------------
|
||||
|
||||
def isBDS44(msg, rev=False):
|
||||
"""Check if a message is likely to be BDS code 4,4
|
||||
Meteorological routine air report
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
rev (bool): using revised version
|
||||
|
||||
Returns:
|
||||
bool: True or False
|
||||
"""
|
||||
|
||||
if isnull(msg):
|
||||
return False
|
||||
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
result = True
|
||||
|
||||
if not rev:
|
||||
# status bit 5, 35, 47, 50
|
||||
result = result & checkbits(d, 5, 6, 23) \
|
||||
& checkbits(d, 35, 36, 46) & checkbits(d, 47, 48, 49) \
|
||||
& checkbits(d, 50, 51, 56)
|
||||
|
||||
else:
|
||||
# status bit 5, 15, 24, 36, 49
|
||||
result = result & checkbits(d, 5, 6, 14) \
|
||||
& checkbits(d, 15, 16, 23) & checkbits(d, 24, 25, 35) \
|
||||
& checkbits(d, 36, 37, 47) & checkbits(d, 49, 50, 56)
|
||||
|
||||
if not result:
|
||||
return False
|
||||
|
||||
vw = wind44(msg, rev=rev)
|
||||
if vw is not None and vw[0] > 250:
|
||||
result &= False
|
||||
|
||||
# if temp44(msg):
|
||||
# if temp44(msg) > 60 or temp44(msg) < -80:
|
||||
# result &= False
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def wind44(msg, rev=False):
|
||||
"""reported wind speed and direction
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS44) string
|
||||
rev (bool): using revised version
|
||||
|
||||
Returns:
|
||||
(int, float): speed (kt), direction (degree)
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
if not rev:
|
||||
status = int(d[4])
|
||||
if not status:
|
||||
return None
|
||||
|
||||
speed = util.bin2int(d[5:14]) # knots
|
||||
direction = util.bin2int(d[14:23]) * 180.0 / 256.0 # degree
|
||||
|
||||
else:
|
||||
spd_status = int(d[4])
|
||||
dir_status = int(d[14])
|
||||
|
||||
if (not spd_status) or (not dir_status):
|
||||
return None
|
||||
|
||||
speed = util.bin2int(d[5:14]) # knots
|
||||
direction = util.bin2int(d[15:23]) * 180.0 / 128.0 # degree
|
||||
|
||||
return round(speed, 0), round(direction, 1)
|
||||
|
||||
|
||||
def temp44(msg, rev=False):
|
||||
"""reported air temperature
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS44) string
|
||||
rev (bool): using revised version
|
||||
|
||||
Returns:
|
||||
float: tmeperature in Celsius degree
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
if not rev:
|
||||
# if d[22] == '0':
|
||||
# return None
|
||||
|
||||
sign = int(d[23])
|
||||
value = util.bin2int(d[24:34])
|
||||
|
||||
if sign:
|
||||
value = value - 1024
|
||||
|
||||
temp = value * 0.125 # celsius
|
||||
temp = round(temp, 1)
|
||||
else:
|
||||
# if d[23] == '0':
|
||||
# return None
|
||||
|
||||
sign = int(d[24])
|
||||
value = util.bin2int(d[25:35])
|
||||
|
||||
if sign:
|
||||
value = value - 1024
|
||||
|
||||
temp = value * 0.125 # celsius
|
||||
temp = round(temp, 1)
|
||||
|
||||
return -1*temp if sign else temp
|
||||
|
||||
|
||||
def p44(msg, rev=False):
|
||||
"""reported average static pressure
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS44) string
|
||||
rev (bool): using revised version
|
||||
|
||||
Returns:
|
||||
int: static pressure in hPa
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
if not rev:
|
||||
if d[34] == '0':
|
||||
return None
|
||||
|
||||
p = util.bin2int(d[35:46]) # hPa
|
||||
|
||||
else:
|
||||
if d[35] == '0':
|
||||
return None
|
||||
|
||||
p = util.bin2int(d[36:47]) # hPa
|
||||
|
||||
return p
|
||||
|
||||
|
||||
def hum44(msg, rev=False):
|
||||
"""reported humidity
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS44) string
|
||||
rev (bool): using revised version
|
||||
|
||||
Returns:
|
||||
float: percentage of humidity, [0 - 100] %
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
if not rev:
|
||||
if d[49] == '0':
|
||||
return None
|
||||
|
||||
hm = util.bin2int(d[50:56]) * 100.0 / 64 # %
|
||||
|
||||
else:
|
||||
if d[48] == '0':
|
||||
return None
|
||||
|
||||
hm = util.bin2int(d[49:56]) # %
|
||||
|
||||
return round(hm, 1)
|
||||
|
||||
|
||||
# ------------------------------------------
|
||||
# BDS 5,0
|
||||
# Track and turn report
|
||||
# ------------------------------------------
|
||||
|
||||
def isBDS50(msg):
|
||||
"""Check if a message is likely to be BDS code 5,0
|
||||
(Track and turn report)
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
bool: True or False
|
||||
"""
|
||||
|
||||
if isnull(msg):
|
||||
return False
|
||||
|
||||
# status bit 1, 12, 24, 35, 46
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
result = True
|
||||
|
||||
result = result & checkbits(d, 1, 3, 11) & checkbits(d, 12, 13, 23) \
|
||||
& checkbits(d, 24, 25, 34) & checkbits(d, 35, 36, 45) \
|
||||
& checkbits(d, 46, 47, 56)
|
||||
|
||||
if not result:
|
||||
return False
|
||||
|
||||
if d[2:11] == "000000000":
|
||||
result &= True
|
||||
else:
|
||||
roll = abs(roll50(msg))
|
||||
if roll and roll > 60:
|
||||
result &= False
|
||||
|
||||
gs = gs50(msg)
|
||||
if gs is not None and gs > 600:
|
||||
result &= False
|
||||
|
||||
tas = tas50(msg)
|
||||
if tas is not None and tas > 500:
|
||||
result &= False
|
||||
|
||||
if (gs is not None) and (tas is not None) and (abs(tas - gs) > 200):
|
||||
result &= False
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def roll50(msg):
|
||||
"""Roll angle, BDS 5,0 message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS50) string
|
||||
|
||||
Returns:
|
||||
float: angle in degrees,
|
||||
negative->left wing down, positive->right wing down
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
if d[0] == '0':
|
||||
return None
|
||||
|
||||
sign = int(d[1]) # 1 -> left wing down
|
||||
value = util.bin2int(d[2:11])
|
||||
|
||||
if sign:
|
||||
value = value - 512
|
||||
|
||||
angle = value * 45.0 / 256.0 # degree
|
||||
return round(angle, 1)
|
||||
|
||||
|
||||
def trk50(msg):
|
||||
"""True track angle, BDS 5,0 message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS50) string
|
||||
|
||||
Returns:
|
||||
float: angle in degrees to true north (from 0 to 360)
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
if d[11] == '0':
|
||||
return None
|
||||
|
||||
sign = int(d[12]) # 1 -> west
|
||||
value = util.bin2int(d[13:23])
|
||||
|
||||
if sign:
|
||||
value = value - 1024
|
||||
|
||||
trk = value * 90.0 / 512.0
|
||||
|
||||
# convert from [-180, 180] to [0, 360]
|
||||
if trk < 0:
|
||||
trk = 360 + trk
|
||||
|
||||
return round(trk, 1)
|
||||
|
||||
|
||||
def gs50(msg):
|
||||
"""Ground speed, BDS 5,0 message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS50) string
|
||||
|
||||
Returns:
|
||||
int: ground speed in knots
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
if d[23] == '0':
|
||||
return None
|
||||
|
||||
spd = util.bin2int(d[24:34]) * 2 # kts
|
||||
return spd
|
||||
|
||||
|
||||
def rtrk50(msg):
|
||||
"""Track angle rate, BDS 5,0 message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS50) string
|
||||
|
||||
Returns:
|
||||
float: angle rate in degrees/second
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
if d[34] == '0':
|
||||
return None
|
||||
|
||||
if d[36:45] == "111111111":
|
||||
return None
|
||||
|
||||
sign = int(d[35]) # 1 -> minus
|
||||
value = util.bin2int(d[36:45])
|
||||
if sign:
|
||||
value = value - 512
|
||||
|
||||
angle = value * 8.0 / 256.0 # degree / sec
|
||||
return round(angle, 3)
|
||||
|
||||
|
||||
def tas50(msg):
|
||||
"""Aircraft true airspeed, BDS 5,0 message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS50) string
|
||||
|
||||
Returns:
|
||||
int: true airspeed in knots
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
if d[45] == '0':
|
||||
return None
|
||||
|
||||
tas = util.bin2int(d[46:56]) * 2 # kts
|
||||
return tas
|
||||
|
||||
|
||||
# ------------------------------------------
|
||||
# BDS 5,3
|
||||
# Air-referenced state vector
|
||||
# ------------------------------------------
|
||||
|
||||
def isBDS53(msg):
|
||||
"""Check if a message is likely to be BDS code 5,3
|
||||
(Air-referenced state vector)
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
bool: True or False
|
||||
"""
|
||||
|
||||
if isnull(msg):
|
||||
return False
|
||||
|
||||
# status bit 1, 13, 24, 34, 47
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
result = True
|
||||
|
||||
result = result & checkbits(d, 1, 3, 12) & checkbits(d, 13, 14, 23) \
|
||||
& checkbits(d, 24, 25, 33) & checkbits(d, 34, 35, 46) \
|
||||
& checkbits(d, 47, 49, 56)
|
||||
|
||||
if not result:
|
||||
return False
|
||||
|
||||
ias = ias53(msg)
|
||||
if ias is not None and ias > 500:
|
||||
result &= False
|
||||
|
||||
mach = mach53(msg)
|
||||
if mach is not None and mach > 1:
|
||||
result &= False
|
||||
|
||||
tas = tas53(msg)
|
||||
if tas is not None and tas > 500:
|
||||
result &= False
|
||||
|
||||
vr = vr53(msg)
|
||||
if vr is not None and abs(vr) > 8000:
|
||||
result &= False
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def hdg53(msg):
|
||||
"""Magnetic heading, BDS 5,3 message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS53) string
|
||||
|
||||
Returns:
|
||||
float: angle in degrees to true north (from 0 to 360)
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
if d[0] == '0':
|
||||
return None
|
||||
|
||||
sign = int(d[1]) # 1 -> west
|
||||
value = util.bin2int(d[2:12])
|
||||
|
||||
if sign:
|
||||
value = value - 1024
|
||||
|
||||
hdg = value * 90.0 / 512.0 # degree
|
||||
|
||||
# convert from [-180, 180] to [0, 360]
|
||||
if hdg < 0:
|
||||
hdg = 360 + hdg
|
||||
|
||||
return round(hdg, 1)
|
||||
|
||||
|
||||
def ias53(msg):
|
||||
"""Indicated airspeed, DBS 5,3 message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message
|
||||
|
||||
Returns:
|
||||
int: indicated arispeed in knots
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
if d[12] == '0':
|
||||
return None
|
||||
|
||||
ias = util.bin2int(d[13:23]) # knots
|
||||
return ias
|
||||
|
||||
|
||||
def mach53(msg):
|
||||
"""MACH number, DBS 5,3 message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message
|
||||
|
||||
Returns:
|
||||
float: MACH number
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
if d[23] == '0':
|
||||
return None
|
||||
|
||||
mach = util.bin2int(d[24:33]) * 0.008
|
||||
return round(mach, 3)
|
||||
|
||||
|
||||
def tas53(msg):
|
||||
"""Aircraft true airspeed, BDS 5,3 message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message
|
||||
|
||||
Returns:
|
||||
float: true airspeed in knots
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
if d[33] == '0':
|
||||
return None
|
||||
|
||||
tas = util.bin2int(d[34:46]) * 0.5 # kts
|
||||
return round(tas, 1)
|
||||
|
||||
def vr53(msg):
|
||||
"""Vertical rate
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS60) string
|
||||
|
||||
Returns:
|
||||
int: vertical rate in feet/minutes
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
if d[46] == '0':
|
||||
return None
|
||||
|
||||
sign = d[47] # 1 -> minus
|
||||
value = util.bin2int(d[48:56])
|
||||
|
||||
if sign:
|
||||
value = value - 256
|
||||
roc = value * 64 # feet/min
|
||||
return roc
|
||||
|
||||
|
||||
# ------------------------------------------
|
||||
# BDS 6,0
|
||||
# ------------------------------------------
|
||||
|
||||
def isBDS60(msg):
|
||||
"""Check if a message is likely to be BDS code 6,0
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
bool: True or False
|
||||
"""
|
||||
|
||||
if isnull(msg):
|
||||
return False
|
||||
|
||||
# status bit 1, 13, 24, 35, 46
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
result = True
|
||||
|
||||
result = result & checkbits(d, 1, 2, 12) & checkbits(d, 13, 14, 23) \
|
||||
& checkbits(d, 24, 25, 34) & checkbits(d, 35, 36, 45) \
|
||||
& checkbits(d, 46, 47, 56)
|
||||
|
||||
if not result:
|
||||
return False
|
||||
|
||||
ias = ias60(msg)
|
||||
if ias is not None and ias > 500:
|
||||
result &= False
|
||||
|
||||
mach = mach60(msg)
|
||||
if mach is not None and mach > 1:
|
||||
result &= False
|
||||
|
||||
# leave out the check from vertical rates,
|
||||
# due to very noisy measurement
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def hdg60(msg):
|
||||
"""Megnetic heading of aircraft
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS60) string
|
||||
|
||||
Returns:
|
||||
float: heading in degrees to megnetic north (from 0 to 360)
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
if d[0] == '0':
|
||||
return None
|
||||
|
||||
sign = int(d[1]) # 1 -> west
|
||||
value = util.bin2int(d[2:12])
|
||||
|
||||
if sign:
|
||||
value = value - 1024
|
||||
|
||||
hdg = value * 90 / 512.0 # degree
|
||||
|
||||
# convert from [-180, 180] to [0, 360]
|
||||
if hdg < 0:
|
||||
hdg = 360 + hdg
|
||||
|
||||
return round(hdg, 1)
|
||||
|
||||
|
||||
def ias60(msg):
|
||||
"""Indicated airspeed
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS60) string
|
||||
|
||||
Returns:
|
||||
int: indicated airspeed in knots
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
if d[12] == '0':
|
||||
return None
|
||||
|
||||
ias = util.bin2int(d[13:23]) # kts
|
||||
return ias
|
||||
|
||||
|
||||
def mach60(msg):
|
||||
"""Aircraft MACH number
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS60) string
|
||||
|
||||
Returns:
|
||||
float: MACH number
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
if d[23] == '0':
|
||||
return None
|
||||
|
||||
mach = util.bin2int(d[24:34]) * 2.048 / 512.0
|
||||
return round(mach, 3)
|
||||
|
||||
|
||||
def vr60baro(msg):
|
||||
"""Vertical rate from barometric measurement, this value may be very noisy.
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS60) string
|
||||
|
||||
Returns:
|
||||
int: vertical rate in feet/minutes
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
if d[34] == '0':
|
||||
return None
|
||||
|
||||
sign = d[35] # 1 -> minus
|
||||
value = util.bin2int(d[36:45])
|
||||
|
||||
if sign:
|
||||
value = value - 512
|
||||
|
||||
roc = value * 32 # feet/min
|
||||
return roc
|
||||
|
||||
|
||||
def vr60ins(msg):
|
||||
"""Vertical rate messured by onbard equiments (IRS, AHRS)
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS60) string
|
||||
|
||||
Returns:
|
||||
int: vertical rate in feet/minutes
|
||||
"""
|
||||
d = util.hex2bin(data(msg))
|
||||
|
||||
if d[45] == '0':
|
||||
return None
|
||||
|
||||
sign = d[46] # 1 -> minus
|
||||
value = util.bin2int(d[47:56])
|
||||
|
||||
if sign:
|
||||
value = value - 512
|
||||
|
||||
roc = value * 32 # feet/min
|
||||
return roc
|
||||
|
||||
|
||||
def BDS(msg):
|
||||
"""Estimate the most likely BDS code of an message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
String or None: Version: "BDS20", "BDS40", "BDS50", or "BDS60". Or None, if nothing matched
|
||||
"""
|
||||
|
||||
if isnull(msg):
|
||||
return None
|
||||
|
||||
is17 = isBDS17(msg)
|
||||
is20 = isBDS20(msg)
|
||||
is40 = isBDS40(msg)
|
||||
is44 = isBDS44(msg)
|
||||
is44rev = isBDS44(msg, rev=True)
|
||||
is50 = isBDS50(msg)
|
||||
is53 = isBDS53(msg)
|
||||
is60 = isBDS60(msg)
|
||||
|
||||
BDS = ["BDS17", "BDS20", "BDS40", "BDS44", "BDS44REV", "BDS50", "BDS53", "BDS60"]
|
||||
isBDS = [is17, is20, is40, is44, is44rev, is50, is53, is60]
|
||||
|
||||
if sum(isBDS) == 0:
|
||||
return None
|
||||
elif sum(isBDS) == 1:
|
||||
return BDS[isBDS.index(True)]
|
||||
else:
|
||||
return [bds for (bds, i) in zip(BDS, isBDS) if i]
|
||||
@@ -1,37 +0,0 @@
|
||||
from __future__ import absolute_import, print_function, division
|
||||
from . import util, modes_common
|
||||
|
||||
def icao(msg):
|
||||
return modes_common.icao(msg)
|
||||
|
||||
|
||||
def df4alt(msg):
|
||||
"""Computes the altitude from DF4 message, bit 20-32
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
int: altitude in ft
|
||||
"""
|
||||
|
||||
if util.df(msg) != 4:
|
||||
raise RuntimeError("Message must be Downlink Format 4.")
|
||||
|
||||
return modes_common.altcode(msg)
|
||||
|
||||
|
||||
def df5id(msg):
|
||||
"""Computes identity (squawk code) from DF5 message, bit 20-32
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
string: squawk code
|
||||
"""
|
||||
|
||||
if util.df(msg) != 5:
|
||||
raise RuntimeError("Message must be Downlink Format 5.")
|
||||
|
||||
return modes_common.idcode(msg)
|
||||
1
pyModeS/extra/__init__.py
Normal file
1
pyModeS/extra/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from __future__ import absolute_import, print_function, division
|
||||
184
pyModeS/extra/aero.py
Normal file
184
pyModeS/extra/aero.py
Normal file
@@ -0,0 +1,184 @@
|
||||
"""
|
||||
Functions for aeronautics in this module
|
||||
|
||||
- physical quantities always in SI units
|
||||
- lat,lon,course and heading in degrees
|
||||
|
||||
International Standard Atmosphere
|
||||
::
|
||||
|
||||
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]
|
||||
p = pressure(H) # calls atmos but retruns only pressure [Pa]
|
||||
T = temperature(H) # calculates temperature [K]
|
||||
rho = density(H) # calls atmos but retruns only pressure [Pa]
|
||||
|
||||
Speed conversion at altitude H[m] in ISA
|
||||
::
|
||||
|
||||
Mach = tas2mach(Vtas,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]
|
||||
Veas = tas2eas(Vtas,H) # true airspeed to equivent airspeed, H in [m]
|
||||
Vtas = cas2tas(Vcas,H) # Vcas to Vtas 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]
|
||||
Mach = cas2mach(Vcas,H) # Vcas to mach copnversion Vcas in m/s, H in [m]
|
||||
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
|
||||
"""Aero and geo Constants """
|
||||
kts = 0.514444 # knot -> m/s
|
||||
ft = 0.3048 # ft -> m
|
||||
fpm = 0.00508 # ft/min -> m/s
|
||||
inch = 0.0254 # inch -> m
|
||||
sqft = 0.09290304 # 1 square foot
|
||||
nm = 1852. # nautical mile -> m
|
||||
lbs = 0.453592 # pound -> kg
|
||||
g0 = 9.80665 # m/s2, Sea level gravity constant
|
||||
R = 287.05287 # m2/(s2 x K), gas constant, sea level ISA
|
||||
p0 = 101325. # Pa, air pressure, sea level ISA
|
||||
rho0 = 1.225 # kg/m3, air density, sea level ISA
|
||||
T0 = 288.15 # K, temperature, sea level ISA
|
||||
gamma = 1.40 # cp/cv for air
|
||||
gamma1 = 0.2 # (gamma-1)/2 for air
|
||||
gamma2 = 3.5 # gamma/(gamma-1) for air
|
||||
beta = -0.0065 # [K/m] ISA temp gradient below tropopause
|
||||
r_earth = 6371000. # m, average earth radius
|
||||
a0 = 340.293988 # m/s, sea level speed of sound ISA, sqrt(gamma*R*T0)
|
||||
|
||||
|
||||
def atmos(H):
|
||||
# H in metres
|
||||
T = np.maximum(288.15 - 0.0065 * H, 216.65)
|
||||
rhotrop = 1.225 * (T / 288.15)**4.256848030018761
|
||||
dhstrat = np.maximum(0., H - 11000.0)
|
||||
rho = rhotrop * np.exp(-dhstrat / 6341.552161)
|
||||
p = rho * R * T
|
||||
return p, rho, T
|
||||
|
||||
|
||||
def temperature(H):
|
||||
p, r, T = atmos(H)
|
||||
return T
|
||||
|
||||
|
||||
def pressure(H):
|
||||
p, r, T = atmos(H)
|
||||
return p
|
||||
|
||||
|
||||
def density(H):
|
||||
p, r, T = atmos(H)
|
||||
return r
|
||||
|
||||
|
||||
def vsound(H):
|
||||
"""Speed of sound"""
|
||||
T = temperature(H)
|
||||
a = np.sqrt(gamma * R * T)
|
||||
return a
|
||||
|
||||
|
||||
def distance(lat1, lon1, lat2, lon2, H=0):
|
||||
"""
|
||||
Compute spherical distance from spherical coordinates.
|
||||
|
||||
For two locations in spherical coordinates
|
||||
(1, theta, phi) and (1, theta', phi')
|
||||
cosine( arc length ) =
|
||||
sin phi sin phi' cos(theta-theta') + cos phi cos phi'
|
||||
distance = rho * arc length
|
||||
"""
|
||||
|
||||
# phi = 90 - latitude
|
||||
phi1 = np.radians(90.0 - lat1)
|
||||
phi2 = np.radians(90.0 - lat2)
|
||||
|
||||
# theta = longitude
|
||||
theta1 = np.radians(lon1)
|
||||
theta2 = np.radians(lon2)
|
||||
|
||||
cos = np.sin(phi1) * np.sin(phi2) * np.cos(theta1 - theta2) + np.cos(phi1) * np.cos(phi2)
|
||||
cos = np.where(cos>1, 1, cos)
|
||||
|
||||
arc = np.arccos(cos)
|
||||
dist = arc * (r_earth + H) # meters, radius of earth
|
||||
return dist
|
||||
|
||||
|
||||
def bearing(lat1, lon1, lat2, lon2):
|
||||
lat1 = np.radians(lat1)
|
||||
lon1 = np.radians(lon1)
|
||||
lat2 = np.radians(lat2)
|
||||
lon2 = np.radians(lon2)
|
||||
x = np.sin(lon2-lon1) * np.cos(lat2)
|
||||
y = np.cos(lat1) * np.sin(lat2) \
|
||||
- np.sin(lat1) * np.cos(lat2) * np.cos(lon2-lon1)
|
||||
initial_bearing = np.arctan2(x, y)
|
||||
initial_bearing = np.degrees(initial_bearing)
|
||||
bearing = (initial_bearing + 360) % 360
|
||||
return bearing
|
||||
|
||||
|
||||
# -----------------------------------------------------
|
||||
# Speed conversions, altitude H all in meters
|
||||
# -----------------------------------------------------
|
||||
def tas2mach(Vtas, H):
|
||||
"""True Airspeed to Mach number"""
|
||||
a = vsound(H)
|
||||
Mach = Vtas/a
|
||||
return Mach
|
||||
|
||||
|
||||
def mach2tas(Mach, H):
|
||||
"""Mach number to True Airspeed"""
|
||||
a = vsound(H)
|
||||
Vtas = Mach*a
|
||||
return Vtas
|
||||
|
||||
|
||||
def eas2tas(Veas, H):
|
||||
"""Equivalent Airspeed to True Airspeed"""
|
||||
rho = density(H)
|
||||
Vtas = Veas * np.sqrt(rho0/rho)
|
||||
return Vtas
|
||||
|
||||
|
||||
def tas2eas(Vtas, H):
|
||||
"""True Airspeed to Equivalent Airspeed"""
|
||||
rho = density(H)
|
||||
Veas = Vtas * np.sqrt(rho/rho0)
|
||||
return Veas
|
||||
|
||||
|
||||
def cas2tas(Vcas, H):
|
||||
"""Calibrated Airspeed to True Airspeed"""
|
||||
p, rho, T = atmos(H)
|
||||
qdyn = p0*((1.+rho0*Vcas*Vcas/(7.*p0))**3.5-1.)
|
||||
Vtas = np.sqrt(7.*p/rho*((1.+qdyn/p)**(2./7.)-1.))
|
||||
return Vtas
|
||||
|
||||
|
||||
def tas2cas(Vtas, H):
|
||||
"""True Airspeed to Calibrated Airspeed"""
|
||||
p, rho, T = atmos(H)
|
||||
qdyn = p*((1.+rho*Vtas*Vtas/(7.*p))**3.5-1.)
|
||||
Vcas = np.sqrt(7.*p0/rho0*((qdyn/p0+1.)**(2./7.)-1.))
|
||||
return Vcas
|
||||
|
||||
|
||||
def mach2cas(Mach, H):
|
||||
"""Mach number to Calibrated Airspeed"""
|
||||
Vtas = mach2tas(Mach, H)
|
||||
Vcas = tas2cas(Vtas, H)
|
||||
return Vcas
|
||||
|
||||
|
||||
def cas2mach(Vcas, H):
|
||||
"""Calibrated Airspeed to Mach number"""
|
||||
Vtas = cas2tas(Vcas, H)
|
||||
Mach = tas2mach(Vtas, H)
|
||||
return Mach
|
||||
302
pyModeS/extra/tcpclient.py
Normal file
302
pyModeS/extra/tcpclient.py
Normal file
@@ -0,0 +1,302 @@
|
||||
'''
|
||||
Stream beast raw data from a TCP server, convert to mode-s messages
|
||||
'''
|
||||
from __future__ import print_function, division
|
||||
import os
|
||||
import sys
|
||||
import socket
|
||||
import time
|
||||
import pyModeS as pms
|
||||
from threading import Thread
|
||||
import traceback
|
||||
|
||||
if (sys.version_info > (3, 0)):
|
||||
PY_VERSION = 3
|
||||
else:
|
||||
PY_VERSION = 2
|
||||
|
||||
class BaseClient(Thread):
|
||||
def __init__(self, host, port, rawtype):
|
||||
Thread.__init__(self)
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.buffer = []
|
||||
self.rawtype = rawtype
|
||||
if self.rawtype not in ['avr', 'beast', 'skysense']:
|
||||
print("rawtype must be either avr, beast or skysense")
|
||||
os._exit(1)
|
||||
|
||||
def connect(self):
|
||||
while True:
|
||||
try:
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.settimeout(10) # 10 second timeout
|
||||
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 read_avr_buffer(self):
|
||||
# -- testing --
|
||||
# for b in self.buffer:
|
||||
# print(chr(b), b)
|
||||
|
||||
# Append message with 0-9,A-F,a-f, until stop sign
|
||||
|
||||
messages = []
|
||||
|
||||
msg_stop = False
|
||||
for b in self.buffer:
|
||||
if b == 59:
|
||||
msg_stop = True
|
||||
ts = time.time()
|
||||
messages.append([self.current_msg, ts])
|
||||
if b == 42:
|
||||
msg_stop = False
|
||||
self.current_msg = ''
|
||||
|
||||
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.buffer = []
|
||||
|
||||
return messages
|
||||
|
||||
def read_beast_buffer(self):
|
||||
'''
|
||||
<esc> "1" : 6 byte MLAT timestamp, 1 byte signal level,
|
||||
2 byte Mode-AC
|
||||
<esc> "2" : 6 byte MLAT timestamp, 1 byte signal level,
|
||||
7 byte Mode-S short frame
|
||||
<esc> "3" : 6 byte MLAT timestamp, 1 byte signal level,
|
||||
14 byte Mode-S long frame
|
||||
<esc> "4" : 6 byte MLAT timestamp, status data, DIP switch
|
||||
configuration settings (not on Mode-S Beast classic)
|
||||
<esc><esc>: true 0x1a
|
||||
<esc> is 0x1a, and "1", "2" and "3" are 0x31, 0x32 and 0x33
|
||||
|
||||
timestamp:
|
||||
wiki.modesbeast.com/Radarcape:Firmware_Versions#The_GPS_timestamp
|
||||
'''
|
||||
|
||||
messages_mlat = []
|
||||
msg = []
|
||||
i = 0
|
||||
|
||||
# process the buffer until the last divider <esc> 0x1a
|
||||
# then, reset the self.buffer with the remainder
|
||||
|
||||
while i < len(self.buffer):
|
||||
if (self.buffer[i:i+2] == [0x1a, 0x1a]):
|
||||
msg.append(0x1a)
|
||||
i += 1
|
||||
elif (i == len(self.buffer) - 1) and (self.buffer[i] == 0x1a):
|
||||
# special case where the last bit is 0x1a
|
||||
msg.append(0x1a)
|
||||
elif self.buffer[i] == 0x1a:
|
||||
if i == len(self.buffer) - 1:
|
||||
# special case where the last bit is 0x1a
|
||||
msg.append(0x1a)
|
||||
elif len(msg) > 0:
|
||||
messages_mlat.append(msg)
|
||||
msg = []
|
||||
else:
|
||||
msg.append(self.buffer[i])
|
||||
i += 1
|
||||
|
||||
# save the reminder for next reading cycle, if not empty
|
||||
if len(msg) > 0:
|
||||
reminder = []
|
||||
for i, m in enumerate(msg):
|
||||
if (m == 0x1a) and (i < len(msg)-1):
|
||||
# rewind 0x1a, except when it is at the last bit
|
||||
reminder.extend([m, m])
|
||||
else:
|
||||
reminder.append(m)
|
||||
self.buffer = [0x1a] + msg
|
||||
else:
|
||||
self.buffer = []
|
||||
|
||||
# extract messages
|
||||
messages = []
|
||||
for mm in messages_mlat:
|
||||
ts = time.time()
|
||||
|
||||
msgtype = mm[0]
|
||||
# print(''.join('%02X' % i for i in mm))
|
||||
|
||||
if msgtype == 0x32:
|
||||
# Mode-S Short Message, 7 byte, 14-len hexstr
|
||||
msg = ''.join('%02X' % i for i in mm[8:15])
|
||||
elif msgtype == 0x33:
|
||||
# Mode-S Long Message, 14 byte, 28-len hexstr
|
||||
msg = ''.join('%02X' % i for i in mm[8:22])
|
||||
else:
|
||||
# Other message tupe
|
||||
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
|
||||
|
||||
messages.append([msg, ts])
|
||||
return messages
|
||||
|
||||
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
|
||||
----------------------------------------------------------------------------------
|
||||
|
||||
SS field - Start character
|
||||
Position 0:
|
||||
1 byte = 8 bits
|
||||
Start character '$'
|
||||
|
||||
MS field - Payload
|
||||
Postion 1 through 14:
|
||||
14 bytes = 112 bits
|
||||
Mode-S payload
|
||||
In case of DF types that only carry 7 bytes of information
|
||||
position 8 through 14 are set to 0x00.
|
||||
|
||||
TS field - Time stamp
|
||||
Position 15 through 20:
|
||||
6 bytes = 48 bits
|
||||
Time stamp with fields as:
|
||||
|
||||
Lock Status - Status of internal time keeping mechanism
|
||||
Equal to 1 if operating normally
|
||||
Bit 47 - 1 bit
|
||||
|
||||
Time of day in UTC seconds, between 0 and 86399
|
||||
Bits 46 through 30 - 17 bits
|
||||
|
||||
Nanoseconds into current second, between 0 and 999999999
|
||||
Bits 29 through 0 - 30 bits
|
||||
|
||||
RS field - Signal Level
|
||||
Position 21 through 23:
|
||||
3 bytes = 24 bits
|
||||
RSSI (received signal strength indication) and relative
|
||||
noise level with fields
|
||||
|
||||
RNL, Q12.4 unsigned fixed point binary with 4 fractional
|
||||
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_STARTCHAR = 0x24
|
||||
|
||||
if len(self.buffer) <= SS_MSGLENGTH:
|
||||
return None
|
||||
|
||||
messages = []
|
||||
while len(self.buffer) > SS_MSGLENGTH:
|
||||
i = 0
|
||||
if self.buffer[i] == SS_STARTCHAR and self.buffer[i+SS_MSGLENGTH] == SS_STARTCHAR:
|
||||
i += 1
|
||||
if (self.buffer[i]>>7):
|
||||
#Long message
|
||||
payload = self.buffer[i:i+14]
|
||||
else:
|
||||
#Short message
|
||||
payload = self.buffer[i:i+7]
|
||||
msg = ''.join('%02X' % j for j in payload)
|
||||
i += 14 #Both message types use 14 bytes
|
||||
tsbin = self.buffer[i:i+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]
|
||||
ts = sec + nano*1.0e-9
|
||||
i += 6
|
||||
#Signal and noise level - Don't care for now
|
||||
i += 3
|
||||
self.buffer = self.buffer[SS_MSGLENGTH:]
|
||||
messages.append( [msg,ts] )
|
||||
else:
|
||||
self.buffer = self.buffer[1:]
|
||||
return 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))
|
||||
|
||||
def run(self):
|
||||
sock = self.connect()
|
||||
|
||||
while True:
|
||||
try:
|
||||
received = sock.recv(1024)
|
||||
|
||||
if PY_VERSION == 2:
|
||||
received = [ord(i) for i in received]
|
||||
|
||||
self.buffer.extend(received)
|
||||
# print(''.join(x.encode('hex') for x in self.buffer))
|
||||
|
||||
# process self.buffer when it is longer enough
|
||||
# if len(self.buffer) < 2048:
|
||||
# continue
|
||||
# -- Removed!! Cause delay in low data rate scenario --
|
||||
|
||||
if self.rawtype == 'beast':
|
||||
messages = self.read_beast_buffer()
|
||||
elif self.rawtype == 'avr':
|
||||
messages = self.read_avr_buffer()
|
||||
elif self.rawtype == 'skysense':
|
||||
messages = self.read_skysense_buffer()
|
||||
|
||||
if not messages:
|
||||
continue
|
||||
else:
|
||||
self.handle_messages(messages)
|
||||
|
||||
time.sleep(0.001)
|
||||
except Exception as e:
|
||||
|
||||
# Provides the user an option to supply the environment
|
||||
# variable PYMODES_DEBUG to halt the execution
|
||||
# for debugging purposes
|
||||
debug_intent = os.environ.get('PYMODES_DEBUG', 'false')
|
||||
if debug_intent.lower() == 'true':
|
||||
traceback.print_exc()
|
||||
sys.exit()
|
||||
else:
|
||||
print("Unexpected Error:", e)
|
||||
|
||||
try:
|
||||
sock = self.connect()
|
||||
except Exception as e:
|
||||
print("Unexpected Error:", e)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# for testing purpose only
|
||||
host = sys.argv[1]
|
||||
port = int(sys.argv[2])
|
||||
rawtype = sys.argv[3]
|
||||
client = BaseClient(host=host, port=port, rawtype=rawtype)
|
||||
client.daemon = True
|
||||
client.run()
|
||||
@@ -1,131 +0,0 @@
|
||||
from __future__ import absolute_import, print_function, division
|
||||
from . import util
|
||||
|
||||
|
||||
def icao(msg):
|
||||
"""Calculate the ICAO address from an Mode-S message
|
||||
with DF4, DF5, DF20, DF21
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
String: ICAO address in 6 bytes hexadecimal string
|
||||
"""
|
||||
|
||||
if util.df(msg) not in (4, 5, 20, 21):
|
||||
# raise RuntimeError("Message DF must be in (4, 5, 20, 21)")
|
||||
return None
|
||||
|
||||
c0 = util.bin2int(util.crc(msg, encode=True))
|
||||
c1 = util.hex2int(msg[-6:])
|
||||
addr = '%06X' % (c0 ^ c1)
|
||||
return addr
|
||||
|
||||
|
||||
def idcode(msg):
|
||||
"""Computes identity (squawk code) from DF5 or DF21 message, bit 20-32.
|
||||
credit: @fbyrkjeland
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
string: squawk code
|
||||
"""
|
||||
|
||||
if util.df(msg) not in [5, 21]:
|
||||
raise RuntimeError("Message must be Downlink Format 5 or 21.")
|
||||
|
||||
mbin = util.hex2bin(msg)
|
||||
|
||||
C1 = mbin[19]
|
||||
A1 = mbin[20]
|
||||
C2 = mbin[21]
|
||||
A2 = mbin[22]
|
||||
C4 = mbin[23]
|
||||
A4 = mbin[24]
|
||||
# _ = mbin[25]
|
||||
B1 = mbin[26]
|
||||
D1 = mbin[27]
|
||||
B2 = mbin[28]
|
||||
D2 = mbin[29]
|
||||
B4 = mbin[30]
|
||||
D4 = mbin[31]
|
||||
|
||||
byte1 = int(A4+A2+A1, 2)
|
||||
byte2 = int(B4+B2+B1, 2)
|
||||
byte3 = int(C4+C2+C1, 2)
|
||||
byte4 = int(D4+D2+D1, 2)
|
||||
|
||||
return str(byte1) + str(byte2) + str(byte3) + str(byte4)
|
||||
|
||||
|
||||
def altcode(msg):
|
||||
"""Computes the altitude from DF4 or DF20 message, bit 20-32.
|
||||
credit: @fbyrkjeland
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
int: altitude in ft
|
||||
"""
|
||||
|
||||
if util.df(msg) not in [4, 20]:
|
||||
raise RuntimeError("Message must be Downlink Format 4 or 20.")
|
||||
|
||||
# Altitude code, bit 20-32
|
||||
mbin = util.hex2bin(msg)
|
||||
|
||||
mbit = mbin[25] # M bit: 26
|
||||
qbit = mbin[27] # Q bit: 28
|
||||
|
||||
|
||||
if mbit == '0': # unit in ft
|
||||
if qbit == '1': # 25ft interval
|
||||
vbin = mbin[19:25] + mbin[26] + mbin[28:32]
|
||||
alt = util.bin2int(vbin) * 25 - 1000
|
||||
if qbit == '0': # 100ft interval, above 50175ft
|
||||
C1 = mbin[19]
|
||||
A1 = mbin[20]
|
||||
C2 = mbin[21]
|
||||
A2 = mbin[22]
|
||||
C4 = mbin[23]
|
||||
A4 = mbin[24]
|
||||
# _ = mbin[25]
|
||||
B1 = mbin[26]
|
||||
# D1 = mbin[27] # always zero
|
||||
B2 = mbin[28]
|
||||
D2 = mbin[29]
|
||||
B4 = mbin[30]
|
||||
D4 = mbin[31]
|
||||
|
||||
graystr = D2 + D4 + A1 + A2 + A4 + B1 + B2 + B4 + C1 + C2 + C4
|
||||
alt = gray2alt(graystr)
|
||||
|
||||
if mbit == '1': # unit in meter
|
||||
vbin = mbin[19:25] + mbin[26:31]
|
||||
alt = int(util.bin2int(vbin) * 3.28084) # convert to ft
|
||||
|
||||
return alt
|
||||
|
||||
def gray2alt(codestr):
|
||||
gc500 = codestr[:8]
|
||||
n500 = util.gray2int(gc500)
|
||||
|
||||
# in 100-ft step must be converted first
|
||||
gc100 = codestr[8:]
|
||||
n100 = util.gray2int(gc100)
|
||||
|
||||
if n100 in [0, 5, 6]:
|
||||
return None
|
||||
|
||||
if n100 == 7:
|
||||
n100 = 5
|
||||
|
||||
if n500%2:
|
||||
n100 = 6 - n100
|
||||
|
||||
alt = (n500*500 + n100*100) - 1300
|
||||
return alt
|
||||
0
pyModeS/streamer/__init__.py
Normal file
0
pyModeS/streamer/__init__.py
Normal file
120
pyModeS/streamer/modeslive
Executable file
120
pyModeS/streamer/modeslive
Executable file
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from __future__ import print_function, division
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import argparse
|
||||
import curses
|
||||
from threading import Lock
|
||||
import pyModeS as pms
|
||||
from pyModeS.extra.tcpclient import BaseClient
|
||||
from pyModeS.streamer.stream import Stream
|
||||
from pyModeS.streamer.screen import Screen
|
||||
|
||||
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
|
||||
sys.stdout = open(os.devnull, 'w')
|
||||
|
||||
client = ModesClient(host=SERVER, port=PORT, rawtype=RAWTYPE)
|
||||
client.daemon = True
|
||||
client.start()
|
||||
|
||||
stream = Stream(lat0=LAT0, lon0=LON0, dumpto=DUMPTO)
|
||||
|
||||
try:
|
||||
screen = Screen(uncertainty=UNCERTAINTY)
|
||||
screen.daemon = True
|
||||
screen.start()
|
||||
|
||||
while True:
|
||||
if len(ADSB_MSG) > 200:
|
||||
LOCK.acquire()
|
||||
stream.process_raw(ADSB_TS, ADSB_MSG, COMMB_TS, COMMB_MSG)
|
||||
ADSB_MSG = []
|
||||
ADSB_TS = []
|
||||
COMMB_MSG = []
|
||||
COMMB_TS = []
|
||||
LOCK.release()
|
||||
|
||||
acs = stream.get_aircraft()
|
||||
try:
|
||||
screen.update_data(acs)
|
||||
screen.update()
|
||||
time.sleep(0.02)
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
continue
|
||||
|
||||
except KeyboardInterrupt:
|
||||
sys.exit(0)
|
||||
|
||||
finally:
|
||||
curses.endwin()
|
||||
183
pyModeS/streamer/screen.py
Normal file
183
pyModeS/streamer/screen.py
Normal file
@@ -0,0 +1,183 @@
|
||||
from __future__ import print_function, division
|
||||
import os
|
||||
import curses
|
||||
import numpy as np
|
||||
import time
|
||||
from threading import Thread
|
||||
|
||||
COLUMNS = [
|
||||
('call', 10),
|
||||
('lat', 10),
|
||||
('lon', 10),
|
||||
('alt', 7),
|
||||
('gs', 5),
|
||||
('tas', 5),
|
||||
('ias', 5),
|
||||
('mach', 7),
|
||||
('roc', 7),
|
||||
('trk', 10),
|
||||
('hdg', 10),
|
||||
('live', 6),
|
||||
]
|
||||
|
||||
UNCERTAINTY_COLUMNS = [
|
||||
('|', 5),
|
||||
('ver', 4),
|
||||
('HPL', 5),
|
||||
('RCu', 5),
|
||||
('RCv', 5),
|
||||
('HVE', 5),
|
||||
('VVE', 5),
|
||||
('Rc', 4),
|
||||
('VPL', 5),
|
||||
('EPU', 5),
|
||||
('VEPU', 6),
|
||||
('HFOMr', 7),
|
||||
('VFOMr', 7),
|
||||
('PE_RCu', 8),
|
||||
('PE_VPL', 8),
|
||||
]
|
||||
|
||||
class Screen(Thread):
|
||||
def __init__(self, uncertainty=False):
|
||||
Thread.__init__(self)
|
||||
self.screen = curses.initscr()
|
||||
curses.noecho()
|
||||
curses.mousemask(1)
|
||||
self.screen.keypad(True)
|
||||
self.y = 3
|
||||
self.x = 1
|
||||
self.offset = 0
|
||||
self.acs = {}
|
||||
self.lock_icao = None
|
||||
|
||||
self.columns = COLUMNS
|
||||
if uncertainty:
|
||||
self.columns.extend(UNCERTAINTY_COLUMNS)
|
||||
|
||||
|
||||
def reset_cursor_pos(self):
|
||||
self.screen.move(self.y, self.x)
|
||||
|
||||
def update_data(self, acs):
|
||||
self.acs = acs
|
||||
|
||||
def draw_frame(self):
|
||||
self.screen.border(0)
|
||||
self.screen.addstr(0, 2, "Online aircraft [%d] ('Ctrl+C' to exit, 'Enter' to lock one)" % len(self.acs))
|
||||
|
||||
def update(self):
|
||||
if len(self.acs) == 0:
|
||||
return
|
||||
|
||||
resized = curses.is_term_resized(self.scr_h, self.scr_w)
|
||||
if resized is True:
|
||||
self.scr_h, self.scr_w = self.screen.getmaxyx()
|
||||
self.screen.clear()
|
||||
curses.resizeterm(self.scr_h, self.scr_w)
|
||||
|
||||
self.screen.refresh()
|
||||
self.draw_frame()
|
||||
|
||||
row = 1
|
||||
|
||||
header = ' icao'
|
||||
for c, cw in self.columns:
|
||||
header += (cw-len(c))*' ' + c
|
||||
|
||||
# fill end with spaces
|
||||
header += (self.scr_w - 2 - len(header)) * ' '
|
||||
|
||||
if len(header) > self.scr_w - 2:
|
||||
header = header[:self.scr_w-3] + '>'
|
||||
|
||||
|
||||
self.screen.addstr(row, 1, header)
|
||||
|
||||
row +=1
|
||||
self.screen.addstr(row, 1, '-'*(self.scr_w-2))
|
||||
|
||||
icaos = np.array(list(self.acs.keys()))
|
||||
icaos = np.sort(icaos)
|
||||
|
||||
for row in range(3, self.scr_h - 3):
|
||||
icao = None
|
||||
idx = row + self.offset - 3
|
||||
|
||||
if idx > len(icaos) - 1:
|
||||
line = ' '*(self.scr_w-2)
|
||||
|
||||
else:
|
||||
line = ''
|
||||
|
||||
icao = icaos[idx]
|
||||
ac = self.acs[icao]
|
||||
|
||||
line += icao
|
||||
|
||||
for c, cw in self.columns:
|
||||
if c=='|':
|
||||
val = '|'
|
||||
elif c=='live':
|
||||
val = str(int(time.time() - ac[c]))+'s'
|
||||
elif ac[c] is None:
|
||||
val = ''
|
||||
else:
|
||||
val = ac[c]
|
||||
val_str = str(val)
|
||||
line += (cw-len(val_str))*' ' + val_str
|
||||
|
||||
# fill end with spaces
|
||||
line += (self.scr_w - 2 - len(line)) * ' '
|
||||
|
||||
if len(line) > self.scr_w - 2:
|
||||
line = line[:self.scr_w-3] + '>'
|
||||
|
||||
if (icao is not None) and (self.lock_icao == icao):
|
||||
self.screen.addstr(row, 1, line, curses.A_STANDOUT)
|
||||
elif row == self.y:
|
||||
self.screen.addstr(row, 1, line, curses.A_BOLD)
|
||||
else:
|
||||
self.screen.addstr(row, 1, line)
|
||||
|
||||
self.screen.addstr(self.scr_h-3, 1, '-'*(self.scr_w-2))
|
||||
|
||||
total_page = len(icaos) // (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.reset_cursor_pos()
|
||||
|
||||
def run(self):
|
||||
self.draw_frame()
|
||||
self.scr_h, self.scr_w = self.screen.getmaxyx()
|
||||
|
||||
while True:
|
||||
c = self.screen.getch()
|
||||
|
||||
if c == curses.KEY_HOME:
|
||||
self.x = 1
|
||||
self.y = 1
|
||||
elif c == curses.KEY_NPAGE:
|
||||
offset_intent = self.offset + (self.scr_h - 4)
|
||||
if offset_intent < len(self.acs) - 5:
|
||||
self.offset = offset_intent
|
||||
elif c == curses.KEY_PPAGE:
|
||||
offset_intent = self.offset - (self.scr_h - 4)
|
||||
if offset_intent > 0:
|
||||
self.offset = offset_intent
|
||||
else:
|
||||
self.offset = 0
|
||||
elif c == curses.KEY_DOWN :
|
||||
y_intent = self.y + 1
|
||||
if y_intent < self.scr_h - 3:
|
||||
self.y = y_intent
|
||||
elif c == curses.KEY_UP:
|
||||
y_intent = self.y - 1
|
||||
if y_intent > 2:
|
||||
self.y = y_intent
|
||||
elif c == curses.KEY_ENTER or c == 10 or c == 13:
|
||||
self.lock_icao = (self.screen.instr(self.y, 1, 6)).decode()
|
||||
elif c == curses.KEY_F5:
|
||||
self.screen.refresh()
|
||||
self.draw_frame()
|
||||
255
pyModeS/streamer/stream.py
Normal file
255
pyModeS/streamer/stream.py
Normal file
@@ -0,0 +1,255 @@
|
||||
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
|
||||
@@ -1,97 +0,0 @@
|
||||
# Copyright (C) 2015 Junzi Sun (TU Delft)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
"""
|
||||
Common functions for ADS-B and Mode-S EHS decoder
|
||||
"""
|
||||
|
||||
|
||||
import math
|
||||
|
||||
# the polynominal generattor code for CRC
|
||||
GENERATOR = "1111111111111010000001001"
|
||||
|
||||
|
||||
def hex2bin(hexstr):
|
||||
"""Convert a hexdecimal string to binary string, with zero fillings. """
|
||||
scale = 16
|
||||
num_of_bits = len(hexstr) * math.log(scale, 2)
|
||||
binstr = bin(int(hexstr, scale))[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 df(msg):
|
||||
"""Decode Downlink Format vaule, bits 1 to 5."""
|
||||
msgbin = hex2bin(msg)
|
||||
return bin2int(msgbin[0:5])
|
||||
|
||||
|
||||
def crc(msg, encode=False):
|
||||
"""Mode-S Cyclic Redundancy Check
|
||||
Detect if bit error occurs in the Mode-S message
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
encode (bool): True to encode the date only and return the checksum
|
||||
Returns:
|
||||
string: message checksum, or partity bits (encoder)
|
||||
"""
|
||||
|
||||
msgbin = list(hex2bin(msg))
|
||||
|
||||
if encode:
|
||||
msgbin[-24:] = ['0'] * 24
|
||||
|
||||
# loop all bits, except last 24 piraty bits
|
||||
for i in range(len(msgbin)-24):
|
||||
# if 1, perform modulo 2 multiplication,
|
||||
if msgbin[i] == '1':
|
||||
for j in range(len(GENERATOR)):
|
||||
# modulo 2 multiplication = XOR
|
||||
msgbin[i+j] = str((int(msgbin[i+j]) ^ int(GENERATOR[j])))
|
||||
|
||||
# last 24 bits
|
||||
reminder = ''.join(msgbin[-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(math.floor(x))
|
||||
|
||||
|
||||
def gray2int(graystr):
|
||||
"""Convert greycode to binary (DF4, 20 altitude coding)"""
|
||||
num = bin2int(graystr)
|
||||
num ^= (num >> 8)
|
||||
num ^= (num >> 4)
|
||||
num ^= (num >> 2)
|
||||
num ^= (num >> 1)
|
||||
return num
|
||||
63
setup.py
63
setup.py
@@ -14,6 +14,7 @@ Steps for deploying a new verison:
|
||||
|
||||
# Always prefer setuptools over distutils
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
# To use a consistent encoding
|
||||
from codecs import open
|
||||
from os import path
|
||||
@@ -21,73 +22,59 @@ from os import path
|
||||
here = path.abspath(path.dirname(__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()
|
||||
|
||||
setup(
|
||||
name='pyModeS',
|
||||
|
||||
name="pyModeS",
|
||||
# Versions should comply with PEP440. For a discussion on single-sourcing
|
||||
# the version across setup.py and the project code, see
|
||||
# https://packaging.python.org/en/latest/single_source_version.html
|
||||
version='1.2.0',
|
||||
|
||||
description='Python Mode-S Decoder',
|
||||
version="2.2",
|
||||
description="Python Mode-S and ADS-B Decoder",
|
||||
long_description=long_description,
|
||||
|
||||
# The project's main homepage.
|
||||
url='https://github.com/junzis/pyModes',
|
||||
|
||||
url="https://github.com/junzis/pyModeS",
|
||||
# Author details
|
||||
author='Junzi Sun',
|
||||
author_email='j.sun-1@tudelft.nl',
|
||||
|
||||
author="Junzi Sun",
|
||||
author_email="j.sun-1@tudelft.nl",
|
||||
# Choose your license
|
||||
license='GNU GPL v3',
|
||||
|
||||
license="GNU GPL v3",
|
||||
# See https://pypi.python.org/pypi?%3Aaction=list_classifiers
|
||||
classifiers=[
|
||||
# How mature is this project? Common values are
|
||||
# 3 - Alpha
|
||||
# 4 - Beta
|
||||
# 5 - Production/Stable
|
||||
'Development Status :: 3 - Alpha',
|
||||
|
||||
"Development Status :: 4 - Beta",
|
||||
# Indicate who your project is intended for
|
||||
'Intended Audience :: Developers',
|
||||
'Topic :: Software Development :: Build Tools',
|
||||
|
||||
"Intended Audience :: Developers",
|
||||
"Topic :: Software Development :: Build Tools",
|
||||
# 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
|
||||
# that you indicate whether you support Python 2, Python 3 or both.
|
||||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 2.6',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
"Programming Language :: Python :: 2",
|
||||
"Programming Language :: Python :: 2.7",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.3",
|
||||
"Programming Language :: Python :: 3.4",
|
||||
"Programming Language :: Python :: 3.5",
|
||||
"Programming Language :: Python :: 3.6",
|
||||
],
|
||||
|
||||
# What does your project relate to?
|
||||
keywords='Mode-S ADS-B EHS decoding',
|
||||
|
||||
keywords="Mode-S ADS-B EHS decoding",
|
||||
# You can just specify the packages manually here if your project is
|
||||
# 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
|
||||
# this:
|
||||
# py_modules=["my_module"],
|
||||
|
||||
# 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
|
||||
# requirements files see:
|
||||
# https://packaging.python.org/en/latest/requirements.html
|
||||
install_requires=[''],
|
||||
|
||||
install_requires=["numpy", "argparse"],
|
||||
# List additional groups of dependencies here (e.g. development
|
||||
# dependencies). You can install these using the following syntax,
|
||||
# for example:
|
||||
@@ -96,20 +83,17 @@ setup(
|
||||
# 'dev': ['check-manifest'],
|
||||
# 'test': ['coverage'],
|
||||
# },
|
||||
|
||||
# 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
|
||||
# have to be included in MANIFEST.in as well.
|
||||
# package_data={
|
||||
# 'sample': ['package_data.dat'],
|
||||
# },
|
||||
|
||||
# Although 'package_data' is the preferred approach, in some case you may
|
||||
# need to place data files outside of your packages. See:
|
||||
# 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'
|
||||
# data_files=[('my_data', ['data/data_file'])],
|
||||
|
||||
# To provide executable scripts, use entry points in preference to the
|
||||
# "scripts" keyword. Entry points provide cross-platform support and allow
|
||||
# pip to create the appropriate form of executable for the target platform.
|
||||
@@ -118,4 +102,5 @@ setup(
|
||||
# 'sample=sample:main',
|
||||
# ],
|
||||
# },
|
||||
scripts=["pyModeS/streamer/modeslive"],
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from __future__ import print_function
|
||||
from pyModeS import adsb, ehs, util
|
||||
from pyModeS import adsb, ehs
|
||||
|
||||
|
||||
# === Decode sample data file ===
|
||||
|
||||
75
tests/sample_run_commb.py
Normal file
75
tests/sample_run_commb.py
Normal file
@@ -0,0 +1,75 @@
|
||||
from __future__ import print_function
|
||||
from pyModeS import commb, common, bds
|
||||
|
||||
# === Decode sample data file ===
|
||||
|
||||
def bds_info(BDS, m):
|
||||
if BDS == "BDS10":
|
||||
info = [commb.ovc10(m)]
|
||||
|
||||
elif BDS == "BDS17":
|
||||
info = ([i[-2:] for i in commb.cap17(m)])
|
||||
|
||||
elif BDS == "BDS20":
|
||||
info = [commb.cs20(m)]
|
||||
|
||||
elif BDS == "BDS40":
|
||||
info = (commb.selalt40mcp(m), commb.selalt40fms(m), commb.p40baro(m))
|
||||
|
||||
elif BDS == "BDS44":
|
||||
info = (commb.wind44(m), commb.temp44(m), commb.p44(m), commb.hum44(m))
|
||||
|
||||
elif BDS == "BDS44REV":
|
||||
info = (commb.wind44(m, rev=True), commb.temp44(m, rev=True), commb.p44(m, rev=True), commb.hum44(m, rev=True))
|
||||
|
||||
elif BDS == "BDS50":
|
||||
info = (commb.roll50(m), commb.trk50(m), commb.gs50(m), commb.rtrk50(m), commb.tas50(m))
|
||||
|
||||
elif BDS == "BDS60":
|
||||
info = (commb.hdg60(m), commb.ias60(m), commb.mach60(m), commb.vr60baro(m), commb.vr60ins(m))
|
||||
|
||||
else:
|
||||
info = None
|
||||
|
||||
return info
|
||||
|
||||
|
||||
def commb_decode_all(df, n=None):
|
||||
import csv
|
||||
|
||||
print("===== Decode Comm-B sample data (DF=%s)=====" % df)
|
||||
|
||||
f = open('tests/data/sample_data_commb_df%s.csv' % df, 'rt')
|
||||
|
||||
|
||||
for i, r in enumerate(csv.reader(f)):
|
||||
if n and i > n:
|
||||
break
|
||||
|
||||
ts = r[0]
|
||||
m = r[2]
|
||||
|
||||
df = common.df(m)
|
||||
icao = common.icao(m)
|
||||
BDS = bds.infer(m)
|
||||
code = common.altcode(m) if df == 20 else common.idcode(m)
|
||||
|
||||
if not BDS:
|
||||
print(ts, m, icao, df, '%5s'%code, 'UNKNOWN')
|
||||
continue
|
||||
|
||||
if len(BDS.split(",")) > 1:
|
||||
print(ts, m, icao, df, '%5s' % code, end=' ')
|
||||
for i, _bds in enumerate(BDS.split(",")):
|
||||
if i == 0:
|
||||
print(_bds, *bds_info(_bds, m))
|
||||
else:
|
||||
print(' ' * 55, _bds, *bds_info(_bds, m))
|
||||
|
||||
else:
|
||||
print(ts, m, icao, df, '%5s'%code, BDS, *bds_info(BDS, m))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
commb_decode_all(df=20, n=100)
|
||||
commb_decode_all(df=21, n=100)
|
||||
@@ -1,75 +0,0 @@
|
||||
from __future__ import print_function
|
||||
from pyModeS import adsb, ehs, util
|
||||
|
||||
|
||||
# === Decode sample data file ===
|
||||
|
||||
def bds_info(BDS, m):
|
||||
if BDS == "BDS17":
|
||||
info = ([i[-2:] for i in ehs.cap17(m)])
|
||||
|
||||
elif BDS == "BDS20":
|
||||
info = ehs.callsign(m)
|
||||
|
||||
elif BDS == "BDS40":
|
||||
info = (ehs.alt40mcp(m), ehs.alt40fms(m), ehs.p40baro(m))
|
||||
|
||||
elif BDS == "BDS44":
|
||||
info = (ehs.wind44(m), ehs.temp44(m), ehs.p44(m), ehs.hum44(m))
|
||||
|
||||
elif BDS == "BDS44REV":
|
||||
info = (ehs.wind44(m, rev=True), ehs.temp44(m, rev=True), ehs.p44(m, rev=True), ehs.hum44(m, rev=True))
|
||||
|
||||
elif BDS == "BDS50":
|
||||
info = (ehs.roll50(m), ehs.trk50(m), ehs.gs50(m), ehs.rtrk50(m), ehs.tas50(m))
|
||||
|
||||
elif BDS == "BDS53":
|
||||
info = (ehs.hdg53(m), ehs.ias53(m), ehs.mach53(m), ehs.tas53(m), ehs.vr53(m))
|
||||
|
||||
elif BDS == "BDS60":
|
||||
info = (ehs.hdg60(m), ehs.ias60(m), ehs.mach60(m), ehs.vr60baro(m), ehs.vr60ins(m))
|
||||
|
||||
else:
|
||||
info = None
|
||||
|
||||
return info
|
||||
|
||||
|
||||
def ehs_decode_all(df, n=None):
|
||||
import csv
|
||||
|
||||
print("===== Decode EHS sample data (DF=%s)=====" % df)
|
||||
|
||||
f = open('tests/data/sample_data_ehs_df%s.csv' % df, 'rt')
|
||||
|
||||
|
||||
for i, r in enumerate(csv.reader(f)):
|
||||
if n and i > n:
|
||||
break
|
||||
|
||||
ts = r[0]
|
||||
m = r[2]
|
||||
|
||||
df = util.df(m)
|
||||
icao = ehs.icao(m)
|
||||
BDS = ehs.BDS(m)
|
||||
code = ehs.df20alt(m) if df==20 else ehs.df21id(m)
|
||||
|
||||
if not BDS:
|
||||
print(ts, m, icao, df, '%5s'%code, 'UNKNOWN')
|
||||
continue
|
||||
|
||||
if isinstance(BDS, list):
|
||||
print(ts, m, icao, df, '%5s'%code, end=' ')
|
||||
for i, bds in enumerate(BDS):
|
||||
if i == 0:
|
||||
print(bds, *bds_info(bds, m))
|
||||
else:
|
||||
print(' '*55, bds, *bds_info(bds, m))
|
||||
|
||||
else:
|
||||
print(ts, m, icao, df, '%5s'%code, BDS, *bds_info(BDS, m))
|
||||
|
||||
if __name__ == '__main__':
|
||||
ehs_decode_all(df=20, n=100)
|
||||
ehs_decode_all(df=21, n=100)
|
||||
@@ -58,22 +58,22 @@ def test_adsb_velocity():
|
||||
vgs = adsb.velocity("8D485020994409940838175B284F")
|
||||
vas = adsb.velocity("8DA05F219B06B6AF189400CBC33F")
|
||||
vgs_surface = adsb.velocity("8FC8200A3AB8F5F893096B000000")
|
||||
assert vgs == (159, 182.9, -832, 'GS')
|
||||
assert vas == (376, 244.0, -2304, 'AS')
|
||||
assert vgs == (159, 182.88, -832, 'GS')
|
||||
assert vas == (375, 243.98, -2304, 'TAS')
|
||||
assert vgs_surface == (19.0, 42.2, 0 , 'GS')
|
||||
assert adsb.altitude_diff('8D485020994409940838175B284F') == 550
|
||||
|
||||
|
||||
def test_nic():
|
||||
assert adsb.nic('8D3C70A390AB11F55B8C57F65FE6') == 0
|
||||
assert adsb.nic('8DE1C9738A4A430B427D219C8225') == 1
|
||||
assert adsb.nic('8D44058880B50006B1773DC2A7E9') == 2
|
||||
assert adsb.nic('8D44058881B50006B1773DC2A7E9') == 3
|
||||
assert adsb.nic('8D4AB42A78000640000000FA0D0A') == 4
|
||||
assert adsb.nic('8D4405887099F5D9772F37F86CB6') == 5
|
||||
assert adsb.nic('8D4841A86841528E72D9B472DAC2') == 6
|
||||
assert adsb.nic('8D44057560B9760C0B840A51C89F') == 7
|
||||
assert adsb.nic('8D40621D58C382D690C8AC2863A7') == 8
|
||||
assert adsb.nic('8F48511C598D04F12CCF82451642') == 9
|
||||
assert adsb.nic('8DA4D53A50DBF8C6330F3B35458F') == 10
|
||||
assert adsb.nic('8D3C4ACF4859F1736F8E8ADF4D67') == 11
|
||||
# def test_nic():
|
||||
# assert adsb.nic('8D3C70A390AB11F55B8C57F65FE6') == 0
|
||||
# assert adsb.nic('8DE1C9738A4A430B427D219C8225') == 1
|
||||
# assert adsb.nic('8D44058880B50006B1773DC2A7E9') == 2
|
||||
# assert adsb.nic('8D44058881B50006B1773DC2A7E9') == 3
|
||||
# assert adsb.nic('8D4AB42A78000640000000FA0D0A') == 4
|
||||
# assert adsb.nic('8D4405887099F5D9772F37F86CB6') == 5
|
||||
# assert adsb.nic('8D4841A86841528E72D9B472DAC2') == 6
|
||||
# assert adsb.nic('8D44057560B9760C0B840A51C89F') == 7
|
||||
# assert adsb.nic('8D40621D58C382D690C8AC2863A7') == 8
|
||||
# assert adsb.nic('8F48511C598D04F12CCF82451642') == 9
|
||||
# assert adsb.nic('8DA4D53A50DBF8C6330F3B35458F') == 10
|
||||
# assert adsb.nic('8D3C4ACF4859F1736F8E8ADF4D67') == 11
|
||||
|
||||
20
tests/test_bds_inference.py
Normal file
20
tests/test_bds_inference.py
Normal file
@@ -0,0 +1,20 @@
|
||||
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'
|
||||
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():
|
||||
assert bds.is50or60("A0001838201584F23468207CDFA5", 0, 0, 0) == None
|
||||
assert bds.is50or60("A0000000FFDA9517000464000000", 182, 237, 1250) == 'BDS50'
|
||||
assert bds.is50or60("A0000000919A5927E23444000000", 413, 54, 18700) == 'BDS60'
|
||||
49
tests/test_commb.py
Normal file
49
tests/test_commb.py
Normal file
@@ -0,0 +1,49 @@
|
||||
from pyModeS import bds, commb
|
||||
# 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_'
|
||||
assert commb.cs20("A0001993202422F2E37CE038738E") == 'IBK2873_'
|
||||
|
||||
|
||||
def test_bds40_functions():
|
||||
assert bds.bds40.selalt40mcp("A000029C85E42F313000007047D3") == 3008
|
||||
assert bds.bds40.selalt40fms("A000029C85E42F313000007047D3") == 3008
|
||||
assert bds.bds40.p40baro("A000029C85E42F313000007047D3") == 1020.0
|
||||
|
||||
assert commb.selalt40mcp("A000029C85E42F313000007047D3") == 3008
|
||||
assert commb.selalt40fms("A000029C85E42F313000007047D3") == 3008
|
||||
assert commb.p40baro("A000029C85E42F313000007047D3") == 1020.0
|
||||
|
||||
|
||||
def test_bds50_functions():
|
||||
assert bds.bds50.roll50("A000139381951536E024D4CCF6B5") == 2.1
|
||||
assert bds.bds50.roll50("A0001691FFD263377FFCE02B2BF9") == -0.4 # signed value
|
||||
assert bds.bds50.trk50("A000139381951536E024D4CCF6B5") == 114.258
|
||||
assert bds.bds50.gs50("A000139381951536E024D4CCF6B5") == 438
|
||||
assert bds.bds50.rtrk50("A000139381951536E024D4CCF6B5") == 0.125
|
||||
assert bds.bds50.tas50("A000139381951536E024D4CCF6B5") == 424
|
||||
|
||||
assert commb.roll50("A000139381951536E024D4CCF6B5") == 2.1
|
||||
assert commb.roll50("A0001691FFD263377FFCE02B2BF9") == -0.4 # signed value
|
||||
assert commb.trk50("A000139381951536E024D4CCF6B5") == 114.258
|
||||
assert commb.gs50("A000139381951536E024D4CCF6B5") == 438
|
||||
assert commb.rtrk50("A000139381951536E024D4CCF6B5") == 0.125
|
||||
assert commb.tas50("A000139381951536E024D4CCF6B5") == 424
|
||||
|
||||
|
||||
def test_bds60_functions():
|
||||
assert bds.bds60.hdg60("A00004128F39F91A7E27C46ADC21") == 42.715
|
||||
assert bds.bds60.ias60("A00004128F39F91A7E27C46ADC21") == 252
|
||||
assert bds.bds60.mach60("A00004128F39F91A7E27C46ADC21") == 0.42
|
||||
assert bds.bds60.vr60baro("A00004128F39F91A7E27C46ADC21") == -1920
|
||||
assert bds.bds60.vr60ins("A00004128F39F91A7E27C46ADC21") == -1920
|
||||
|
||||
assert commb.hdg60("A00004128F39F91A7E27C46ADC21") == 42.715
|
||||
assert commb.ias60("A00004128F39F91A7E27C46ADC21") == 252
|
||||
assert commb.mach60("A00004128F39F91A7E27C46ADC21") == 0.42
|
||||
assert commb.vr60baro("A00004128F39F91A7E27C46ADC21") == -1920
|
||||
assert commb.vr60ins("A00004128F39F91A7E27C46ADC21") == -1920
|
||||
58
tests/test_common.py
Normal file
58
tests/test_common.py
Normal file
@@ -0,0 +1,58 @@
|
||||
from pyModeS import common
|
||||
|
||||
|
||||
def test_conversions():
|
||||
assert common.hex2bin('6E406B') == "011011100100000001101011"
|
||||
assert common.bin2hex('011011100100000001101011') == "6E406B"
|
||||
assert common.int2hex(11160538) == "AA4BDA"
|
||||
|
||||
def test_crc_decode():
|
||||
assert common.crc_legacy("8D406B902015A678D4D220AA4BDA") == 0
|
||||
|
||||
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():
|
||||
parity = common.crc("8D406B902015A678D4D220AA4BDA", encode=True)
|
||||
assert common.int2hex(parity) == "AA4BDA"
|
||||
|
||||
def test_icao():
|
||||
assert common.icao("8D406B902015A678D4D220AA4BDA") == "406B90"
|
||||
assert common.icao("A0001839CA3800315800007448D9") == '400940'
|
||||
assert common.icao("A000139381951536E024D4CCF6B5") == '3C4DD2'
|
||||
assert common.icao("A000029CFFBAA11E2004727281F1") == '4243D0'
|
||||
|
||||
def test_modes_altcode():
|
||||
assert common.altcode("A02014B400000000000000F9D514") == 32300
|
||||
|
||||
def test_modes_idcode():
|
||||
assert common.idcode("A800292DFFBBA9383FFCEB903D01") == '1346'
|
||||
|
||||
def test_graycode_to_altitude():
|
||||
assert common.gray2alt('00000000010') == -1000
|
||||
assert common.gray2alt('00000001010') == -500
|
||||
assert common.gray2alt('00000011011') == -100
|
||||
assert common.gray2alt('00000011010') == 0
|
||||
assert common.gray2alt('00000011110') == 100
|
||||
assert common.gray2alt('00000010011') == 600
|
||||
assert common.gray2alt('00000110010') == 1000
|
||||
assert common.gray2alt('00001001001') == 5800
|
||||
assert common.gray2alt('00011100100') == 10300
|
||||
assert common.gray2alt('01100011010') == 32000
|
||||
assert common.gray2alt('01110000100') == 46300
|
||||
assert common.gray2alt('01010101100') == 50200
|
||||
assert common.gray2alt('11011110100') == 73200
|
||||
assert common.gray2alt('10000000011') == 126600
|
||||
assert common.gray2alt('10000000001') == 126700
|
||||
@@ -1,62 +0,0 @@
|
||||
from pyModeS import ehs
|
||||
from pyModeS import modes_common
|
||||
|
||||
def test_ehs_icao():
|
||||
assert ehs.icao("A0001839CA3800315800007448D9") == '400940'
|
||||
assert ehs.icao("A000139381951536E024D4CCF6B5") == '3C4DD2'
|
||||
assert ehs.icao("A000029CFFBAA11E2004727281F1") == '4243D0'
|
||||
|
||||
|
||||
def test_df20alt():
|
||||
assert ehs.df20alt("A02014B400000000000000F9D514") == 32300
|
||||
|
||||
|
||||
def test_ehs_BDS():
|
||||
assert ehs.BDS("A0001838201584F23468207CDFA5") == 'BDS20'
|
||||
assert ehs.BDS("A0001839CA3800315800007448D9") == 'BDS40'
|
||||
assert ehs.BDS("A000031DBAA9DD18622C441330E9") == 'BDS44'
|
||||
assert ehs.BDS("A000139381951536E024D4CCF6B5") == 'BDS50'
|
||||
assert ehs.BDS("A00004128F39F91A7E27C46ADC21") == 'BDS60'
|
||||
|
||||
def test_ehs_BDS20_callsign():
|
||||
assert ehs.callsign("A000083E202CC371C31DE0AA1CCF") == 'KLM1017_'
|
||||
assert ehs.callsign("A0001993202422F2E37CE038738E") == 'IBK2873_'
|
||||
|
||||
|
||||
def test_ehs_BDS40_functions():
|
||||
assert ehs.alt40mcp("A000029C85E42F313000007047D3") == 3008
|
||||
assert ehs.alt40fms("A000029C85E42F313000007047D3") == 3008
|
||||
assert ehs.p40baro("A000029C85E42F313000007047D3") == 1020.0
|
||||
|
||||
def test_ehs_BDS50_functions():
|
||||
assert ehs.roll50("A000139381951536E024D4CCF6B5") == 2.1
|
||||
assert ehs.trk50("A000139381951536E024D4CCF6B5") == 114.3
|
||||
assert ehs.gs50("A000139381951536E024D4CCF6B5") == 438
|
||||
assert ehs.rtrk50("A000139381951536E024D4CCF6B5") == 0.125
|
||||
assert ehs.tas50("A000139381951536E024D4CCF6B5") == 424
|
||||
# signed values
|
||||
assert ehs.roll50("A0001691FFD263377FFCE02B2BF9") == -0.4
|
||||
|
||||
def test_ehs_BDS60_functions():
|
||||
assert ehs.hdg60("A00004128F39F91A7E27C46ADC21") == 42.7
|
||||
assert ehs.ias60("A00004128F39F91A7E27C46ADC21") == 252
|
||||
assert ehs.mach60("A00004128F39F91A7E27C46ADC21") == 0.42
|
||||
assert ehs.vr60baro("A00004128F39F91A7E27C46ADC21") == -1920
|
||||
assert ehs.vr60ins("A00004128F39F91A7E27C46ADC21") == -1920
|
||||
|
||||
def test_graycode_to_altitude():
|
||||
assert modes_common.gray2alt('00000000010') == -1000
|
||||
assert modes_common.gray2alt('00000001010') == -500
|
||||
assert modes_common.gray2alt('00000011011') == -100
|
||||
assert modes_common.gray2alt('00000011010') == 0
|
||||
assert modes_common.gray2alt('00000011110') == 100
|
||||
assert modes_common.gray2alt('00000010011') == 600
|
||||
assert modes_common.gray2alt('00000110010') == 1000
|
||||
assert modes_common.gray2alt('00001001001') == 5800
|
||||
assert modes_common.gray2alt('00011100100') == 10300
|
||||
assert modes_common.gray2alt('01100011010') == 32000
|
||||
assert modes_common.gray2alt('01110000100') == 46300
|
||||
assert modes_common.gray2alt('01010101100') == 50200
|
||||
assert modes_common.gray2alt('11011110100') == 73200
|
||||
assert modes_common.gray2alt('10000000011') == 126600
|
||||
assert modes_common.gray2alt('10000000001') == 126700
|
||||
@@ -1,15 +0,0 @@
|
||||
from pyModeS import util
|
||||
|
||||
|
||||
def test_hex2bin():
|
||||
assert util.hex2bin('6E406B') == "011011100100000001101011"
|
||||
|
||||
|
||||
def test_crc_decode():
|
||||
checksum = util.crc("8D406B902015A678D4D220AA4BDA")
|
||||
assert checksum == "000000000000000000000000"
|
||||
|
||||
|
||||
def test_crc_encode():
|
||||
parity = util.crc("8D406B902015A678D4D220AA4BDA", encode=True)
|
||||
assert util.hex2bin("AA4BDA") == parity
|
||||
Reference in New Issue
Block a user