2018-07-26 02:09:28 +08:00
|
|
|
The Python ADS-B/Mode-S Decoder
|
2019-05-27 16:59:32 +08:00
|
|
|
===============================
|
2016-03-17 06:11:15 +08:00
|
|
|
|
2019-05-27 16:06:43 +08:00
|
|
|
If you find this project useful for your research, please cite our work (bibtex format):
|
|
|
|
|
|
|
|
::
|
|
|
|
|
|
|
|
@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},
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Introduction
|
|
|
|
---------------------
|
|
|
|
PyModeS is a Python library designed to decode Mode-S (including ADS-B) message.
|
|
|
|
Message with following Downlink Formats (DF) are supported:
|
|
|
|
|
2016-03-17 06:11:15 +08:00
|
|
|
|
2018-07-26 02:09:28 +08:00
|
|
|
**DF17 / DF18: Automatic Dependent Surveillance - Broadcast (ADS-B)**
|
2016-03-17 06:11:15 +08:00
|
|
|
|
2018-06-21 04:25:47 +08:00
|
|
|
- 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]
|
2017-07-21 23:45:08 +08:00
|
|
|
|
2017-03-23 04:07:59 +08:00
|
|
|
|
2018-07-26 02:09:28 +08:00
|
|
|
**DF20 / DF21: Mode-S Comm-B replies**
|
2018-06-21 04:22:42 +08:00
|
|
|
|
2018-06-21 04:25:47 +08:00
|
|
|
- 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
|
2019-05-27 16:06:43 +08:00
|
|
|
- BDS 4,4: Meteorological routine air report (experimental)
|
|
|
|
- BDS 4,5: Meteorological hazard report (experimental)
|
2018-06-21 04:25:47 +08:00
|
|
|
- BDS 5,0: Track and turn report
|
|
|
|
- BDS 6,0: Heading and speed report
|
2018-06-21 04:22:42 +08:00
|
|
|
|
|
|
|
|
2018-06-21 04:25:47 +08:00
|
|
|
**DF4 / DF20: Altitude code**
|
|
|
|
|
2018-07-26 02:09:28 +08:00
|
|
|
**DF5 / DF21: Identity code (squawk code)**
|
2016-03-17 06:11:15 +08:00
|
|
|
|
2017-12-21 00:00:02 +08:00
|
|
|
|
2019-05-27 16:06:43 +08:00
|
|
|
Resources
|
2016-03-17 06:11:15 +08:00
|
|
|
-----------
|
2018-07-26 02:09:28 +08:00
|
|
|
Checkout and contribute to this open-source project at:
|
2018-06-21 04:22:42 +08:00
|
|
|
https://github.com/junzis/pyModeS
|
2016-03-17 06:11:15 +08:00
|
|
|
|
2019-05-27 16:06:43 +08:00
|
|
|
Detailed manual on Mode-S decoding is published at:
|
|
|
|
https://mode-s.org/decode.
|
|
|
|
|
|
|
|
API documentation of pyModeS is at:
|
2016-08-16 23:07:02 +08:00
|
|
|
http://pymodes.readthedocs.io
|
2016-03-17 06:11:15 +08:00
|
|
|
|
2017-09-19 17:19:54 +08:00
|
|
|
|
2019-05-27 16:06:43 +08:00
|
|
|
|
2016-03-17 06:11:15 +08:00
|
|
|
Install
|
|
|
|
-------
|
|
|
|
|
2018-07-26 02:09:28 +08:00
|
|
|
To install latest version from the GitHub:
|
2017-12-13 04:48:25 +08:00
|
|
|
|
|
|
|
::
|
|
|
|
|
2018-06-21 04:22:42 +08:00
|
|
|
pip install git+https://github.com/junzis/pyModeS
|
2017-12-13 04:48:25 +08:00
|
|
|
|
2017-09-19 17:19:54 +08:00
|
|
|
|
2019-02-21 21:52:15 +08:00
|
|
|
To install the stable version (2.0) from pip:
|
|
|
|
|
|
|
|
::
|
|
|
|
|
|
|
|
pip install pyModeS
|
|
|
|
|
|
|
|
|
2018-06-23 07:28:41 +08:00
|
|
|
|
2018-07-05 02:56:11 +08:00
|
|
|
Live view traffic (modeslive)
|
2018-06-23 07:28:41 +08:00
|
|
|
----------------------------------------------------
|
2018-06-23 21:19:29 +08:00
|
|
|
Supports **Mode-S Beast** and **AVR** raw stream
|
2018-06-23 07:28:41 +08:00
|
|
|
|
2018-06-23 07:29:58 +08:00
|
|
|
::
|
2018-06-23 07:28:41 +08:00
|
|
|
|
2019-02-08 23:43:02 +08:00
|
|
|
modeslive --server [server_address] --port [tcp_port] --rawtype [beast,avr,skysense] --latlon [lat] [lon] --dumpto [folder]
|
2018-06-23 21:19:29 +08:00
|
|
|
|
|
|
|
Arguments:
|
2019-02-08 23:43:02 +08:00
|
|
|
-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
|
2018-06-23 07:28:41 +08:00
|
|
|
|
|
|
|
|
2018-06-29 20:13:20 +08:00
|
|
|
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]
|
|
|
|
|
2018-07-26 02:09:28 +08:00
|
|
|
Example screenshot:
|
2018-06-23 22:05:06 +08:00
|
|
|
|
2018-07-05 02:56:11 +08:00
|
|
|
.. image:: https://github.com/junzis/pyModeS/raw/master/doc/modeslive-screenshot.png
|
2018-06-23 22:05:06 +08:00
|
|
|
:width: 700px
|
2018-06-23 07:28:41 +08:00
|
|
|
|
2019-05-27 16:59:32 +08:00
|
|
|
|
2017-09-19 17:19:54 +08:00
|
|
|
Use the library
|
|
|
|
---------------
|
2016-03-17 06:11:15 +08:00
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
2017-11-01 18:53:10 +08:00
|
|
|
import pyModeS as pms
|
2016-03-17 06:11:15 +08:00
|
|
|
|
2016-03-23 18:45:38 +08:00
|
|
|
|
2018-07-26 02:09:28 +08:00
|
|
|
Common functions
|
2017-07-21 23:40:10 +08:00
|
|
|
*****************
|
2016-03-23 18:45:38 +08:00
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
2017-11-01 18:53:10 +08:00
|
|
|
pms.df(msg) # Downlink Format
|
2018-03-28 19:31:24 +08:00
|
|
|
pms.icao(msg) # Infer the ICAO address from the message
|
2017-11-01 18:53:10 +08:00
|
|
|
pms.crc(msg, encode=False) # Perform CRC or generate parity bit
|
2016-03-23 18:45:38 +08:00
|
|
|
|
2018-03-28 19:31:24 +08:00
|
|
|
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
|
2017-07-21 23:40:10 +08:00
|
|
|
|
2016-03-23 18:45:38 +08:00
|
|
|
|
2018-07-26 02:09:28 +08:00
|
|
|
Core functions for ADS-B decoding
|
|
|
|
*********************************
|
2016-03-17 06:11:15 +08:00
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
2017-11-01 18:53:10 +08:00
|
|
|
pms.adsb.icao(msg)
|
|
|
|
pms.adsb.typecode(msg)
|
2016-10-19 22:22:28 +08:00
|
|
|
|
2018-07-26 02:09:28 +08:00
|
|
|
# Typecode 1-4
|
2017-11-01 18:53:10 +08:00
|
|
|
pms.adsb.callsign(msg)
|
2016-10-19 22:22:28 +08:00
|
|
|
|
2018-07-26 02:09:28 +08:00
|
|
|
# Typecode 5-8 (surface), 9-18 (airborne, barometric height), and 9-18 (airborne, GNSS height)
|
2017-11-01 18:53:10 +08:00
|
|
|
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)
|
2016-10-19 22:22:28 +08:00
|
|
|
|
2017-11-01 18:53:10 +08:00
|
|
|
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)
|
2017-03-07 20:41:07 +08:00
|
|
|
|
2017-11-01 18:53:10 +08:00
|
|
|
pms.adsb.altitude(msg)
|
|
|
|
|
2018-07-26 02:09:28 +08:00
|
|
|
# Typecode: 19
|
|
|
|
pms.adsb.velocity(msg) # Handles both surface & airborne messages
|
|
|
|
pms.adsb.speed_heading(msg) # Handles both surface & airborne messages
|
2017-11-01 18:53:10 +08:00
|
|
|
pms.adsb.surface_velocity(msg)
|
|
|
|
pms.adsb.airborne_velocity(msg)
|
2017-03-07 20:41:07 +08:00
|
|
|
|
2016-03-17 06:11:15 +08:00
|
|
|
|
2017-09-19 17:19:54 +08:00
|
|
|
Note: When you have a fix position of the aircraft, it is convenient to
|
2016-10-19 22:22:28 +08:00
|
|
|
use `position_with_ref()` method to decode with only one position message
|
|
|
|
(either odd or even). This works with both airborne and surface position
|
|
|
|
messages. But the reference position shall be with in 180NM (airborne)
|
2017-07-21 23:40:10 +08:00
|
|
|
or 45NM (surface) of the true position.
|
|
|
|
|
2018-03-28 19:31:24 +08:00
|
|
|
|
2018-06-21 23:38:59 +08:00
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
2018-03-28 19:31:24 +08:00
|
|
|
Common Mode-S functions
|
|
|
|
************************
|
2017-07-21 23:40:10 +08:00
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
2018-03-28 19:31:24 +08:00
|
|
|
pms.icao(msg) # Infer the ICAO address from the message
|
2018-07-26 02:09:28 +08:00
|
|
|
pms.bds.infer(msg) # Infer the Modes-S BDS register
|
2017-07-21 23:40:10 +08:00
|
|
|
|
2018-07-26 02:09:28 +08:00
|
|
|
# Check if BDS is 5,0 or 6,0, give reference speed, track, altitude (from ADS-B)
|
2018-03-28 19:31:24 +08:00
|
|
|
pms.bds.is50or60(msg, spd_ref, trk_ref, alt_ref)
|
|
|
|
|
2018-07-26 02:09:28 +08:00
|
|
|
# Check each BDS explicitly
|
2018-06-23 21:54:17 +08:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
2018-03-28 19:31:24 +08:00
|
|
|
|
2018-07-26 02:09:28 +08:00
|
|
|
Mode-S Elementary Surveillance (ELS)
|
2018-03-28 19:31:24 +08:00
|
|
|
*************************************
|
2016-03-17 06:11:15 +08:00
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
2018-07-26 02:09:28 +08:00
|
|
|
pms.commb.ovc10(msg) # Overlay capability, BDS 1,0
|
2018-05-17 16:40:22 +08:00
|
|
|
pms.commb.cap17(msg) # GICB capability, BDS 1,7
|
2018-07-26 02:09:28 +08:00
|
|
|
pms.commb.cs20(msg) # Callsign, BDS 2,0
|
2017-11-01 18:53:10 +08:00
|
|
|
|
|
|
|
|
2018-07-26 02:09:28 +08:00
|
|
|
Mode-S Enhanced Surveillance (EHS)
|
2018-03-28 19:31:24 +08:00
|
|
|
***********************************
|
|
|
|
|
|
|
|
.. code:: python
|
2017-11-01 18:53:10 +08:00
|
|
|
|
2019-04-16 22:56:49 +08:00
|
|
|
# BDS 4,0
|
2019-08-05 16:37:17 +08:00
|
|
|
pms.commb.selalt40mcp(msg) # MCP/FCU selected altitude (ft)
|
|
|
|
pms.commb.selalt40fms(msg) # FMS selected altitude (ft)
|
2018-05-17 16:40:22 +08:00
|
|
|
pms.commb.p40baro(msg) # Barometric pressure (mb)
|
2017-11-01 18:53:10 +08:00
|
|
|
|
2019-04-16 22:56:49 +08:00
|
|
|
# BDS 5,0
|
2018-07-26 02:09:28 +08:00
|
|
|
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)
|
2017-11-01 18:53:10 +08:00
|
|
|
|
2019-04-16 22:56:49 +08:00
|
|
|
# BDS 6,0
|
2018-07-26 02:09:28 +08:00
|
|
|
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)
|
2016-03-17 06:11:15 +08:00
|
|
|
|
2018-03-28 19:31:24 +08:00
|
|
|
|
|
|
|
Meteorological routine air report (MRAR) [Experimental]
|
2019-04-16 22:56:49 +08:00
|
|
|
********************************************************
|
|
|
|
|
|
|
|
.. 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]
|
2018-03-28 19:31:24 +08:00
|
|
|
*******************************************************
|
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
2019-04-16 22:56:49 +08:00
|
|
|
# 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)
|
2018-03-28 19:31:24 +08:00
|
|
|
|
2018-06-23 21:54:17 +08:00
|
|
|
|
2019-05-27 16:59:32 +08:00
|
|
|
|
|
|
|
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
|
|
|
|
---------
|
2017-09-19 17:19:54 +08:00
|
|
|
To perform unit tests. First install ``tox`` through pip, Then, run the following commands:
|
2017-11-01 18:53:10 +08:00
|
|
|
|
|
|
|
.. code:: bash
|
|
|
|
|
|
|
|
$ tox
|