17 Commits

Author SHA1 Message Date
Junzi Sun
555b2eea40 reduce complexity and change default type to str 2020-02-26 00:09:52 +01:00
Junzi Sun
dea7cde317 update clean script 2019-10-29 23:59:15 +01:00
Junzi Sun
e16d34bc06 consolidation 2019-10-29 23:55:25 +01:00
Junzi Sun
9bb87b00be update benchmark 2019-10-29 23:39:57 +01:00
Junzi Sun
3dae0438bf update tests 2019-10-29 23:19:37 +01:00
Junzi Sun
02c5117de5 minor updates to C code 2019-10-29 22:33:48 +01:00
Junzi Sun
3f24f78d3a fix hidden altitude() call 2019-10-29 22:24:59 +01:00
Junzi Sun
cf3828d2a0 add make options 2019-10-29 22:24:27 +01:00
Junzi Sun
f70d1f2f1f clean up useless stuff 2019-10-29 18:03:23 +01:00
Junzi Sun
dfeb65fbd7 "make" things easier 2019-10-29 17:53:39 +01:00
Xavier Olive
13b283666a optimisations in bds09 2019-10-29 17:37:45 +01:00
Xavier Olive
6144b88188 bds09 2019-10-29 17:33:32 +01:00
Xavier Olive
b503beb3fd bds08 2019-10-29 16:53:39 +01:00
Xavier Olive
c804cd876c separate cleanly cython and python, bds05, bds06, modulo issues 2019-10-29 16:37:44 +01:00
Xavier Olive
d48caed7e6 add bds05 2019-10-29 11:41:00 +01:00
Xavier Olive
eb675d5ca3 cythonize common 2019-10-29 10:00:01 +01:00
Junzi Sun
b04a1bd49c remove unused functions 2019-10-28 16:24:15 +01:00
89 changed files with 1189 additions and 1754 deletions

2
.gitignore vendored
View File

@@ -6,7 +6,7 @@ __pycache__/
.pytest_cache/ .pytest_cache/
#cython #cython
*.c .c
# C extensions # C extensions
*.so *.so

View File

@@ -8,18 +8,11 @@ ext:
python setup.py build_ext --inplace python setup.py build_ext --inplace
test: test:
make clean python -m pytest
@echo ""
@echo "[Test with py_common]"
python -m pytest tests
@echo ""
@echo "[Test with c_common]"
python setup.py build_ext --inplace
python -m pytest tests
clean: clean:
find pyModeS -type f -name '*.c' -delete find pyModeS/decoder -type f -name '*.c' -delete
find pyModeS -type f -name '*.so' -delete find pyModeS/decoder -type f -name '*.so' -delete
find . | grep -E "(__pycache__|\.pyc|\.pyo$$)" | xargs rm -rf find . | grep -E "(__pycache__|\.pyc|\.pyo$$)" | xargs rm -rf
rm -rf *.egg-info rm -rf *.egg-info
rm -rf .pytest_cache rm -rf .pytest_cache

View File

@@ -1,42 +1,6 @@
The Python ADS-B/Mode-S Decoder The Python ADS-B/Mode-S Decoder
=============================== ===============================
PyModeS is a Python library designed to decode Mode-S (including ADS-B) message. It can be imported to your python project or used as a standalone tool to view and save live traffic data.
This is a project created by Junzi Sun, who works at `TU Delft <https://www.tudelft.nl/en/>`_, `Aerospace Engineering Faculty <https://www.tudelft.nl/en/ae/>`_, `CNS/ATM research group <http://cs.lr.tudelft.nl/atm/>`_. It is supported by many `contributors <https://github.com/junzis/pyModeS/graphs/contributors>`_ from different institutions.
Introduction
------------
pyModeS supports the decoding of following types of messages:
- DF4 / DF20: Altitude code
- DF5 / DF21: Identity code (squawk code)
- DF17 / DF18: Automatic Dependent Surveillance-Broadcast (ADS-B)
- TC=1-4 / BDS 0,8: Aircraft identification and category
- TC=5-8 / BDS 0,6: Surface position
- TC=9-18 / BDS 0,5: Airborne position
- TC=19 / BDS 0,9: Airborne velocity
- TC=28 / BDS 6,1: Airborne status [to be implemented]
- TC=29 / BDS 6,2: Target state and status information [to be implemented]
- TC=31 / BDS 6,5: Aircraft operational status [to be implemented]
- DF20 / DF21: Mode-S Comm-B messages
- BDS 1,0: Data link capability report
- BDS 1,7: Common usage GICB capability report
- BDS 2,0: Aircraft identification
- BDS 3,0: ACAS active resolution advisory
- BDS 4,0: Selected vertical intention
- BDS 4,4: Meteorological routine air report (experimental)
- BDS 4,5: Meteorological hazard report (experimental)
- BDS 5,0: Track and turn report
- BDS 6,0: Heading and speed report
If you find this project useful for your research, please considering cite this tool as:: If you find this project useful for your research, please considering cite this tool as::
@article{sun2019pymodes, @article{sun2019pymodes,
@@ -50,6 +14,40 @@ If you find this project useful for your research, please considering cite this
Introduction
---------------------
PyModeS is a Python library designed to decode Mode-S (including ADS-B) message. It can be imported to your python project or be used as a standalone tool to view and save live traffic data.
Messages 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 Resources
----------- -----------
@@ -57,50 +55,26 @@ Check out and contribute to this open-source project at:
https://github.com/junzis/pyModeS https://github.com/junzis/pyModeS
Detailed manual on Mode-S decoding is published at: Detailed manual on Mode-S decoding is published at:
https://mode-s.org/decode https://mode-s.org/decode.
The API documentation of pyModeS is at: The API documentation of pyModeS is at:
https://mode-s.org/api http://pymodes.readthedocs.io
Basic installation Install
------------------- -------
Installation examples:: Installation examples::
# stable version # stable version
pip install pyModeS pip install pyModeS
# conda (compiled) version
conda install -c conda-forge pymodes
# development version # development version
pip install git+https://github.com/junzis/pyModeS pip install git+https://github.com/junzis/pyModeS
Dependencies ``numpy``, and ``pyzmq`` are installed automatically during previous installations processes. Dependencies ``numpy``, ``pyzmq`` and ``pyrtlsdr`` are installed automatically during previous installations processes.
If you need to connect pyModeS to a RTL-SDR receiver, ``pyrtlsdr`` need to be installed manually::
pip install pyrtlsdr
Advanced installation (using c modules)
------------------------------------------
If you want to make use of the (faster) c module, install ``pyModeS`` as follows::
# conda (compiled) version
conda install -c conda-forge pymodes
# stable version (to be compiled on your side)
pip install pyModeS[fast]
# development version
git clone https://github.com/junzis/pyModeS
cd pyModeS
pip install .[fast]
View live traffic (modeslive) View live traffic (modeslive)
@@ -126,7 +100,7 @@ General usage::
Live with RTL-SDR Live with RTL-SDR
******************* *******************
If you have an RTL-SDR receiver connected to your computer, you can use the ``rtlsdr`` source switch (require ``pyrtlsdr`` package), with command:: If you have an RTL-SDR receiver plugged to the computer, you can connect it with ``rtlsdr`` source switch, shown as follows::
$ modeslive --source rtlsdr $ modeslive --source rtlsdr
@@ -167,7 +141,7 @@ Common functions
pms.hex2bin(str) # Convert hexadecimal string to binary string pms.hex2bin(str) # Convert hexadecimal string to binary string
pms.bin2int(str) # Convert binary string to integer pms.bin2int(str) # Convert binary string to integer
pms.hex2int(str) # Convert hexadecimal string to integer pms.hex2int(str) # Convert hexadecimal string to integer
pms.gray2int(str) # Convert grey code to integer pms.gray2int(str) # Convert grey code to interger
Core functions for ADS-B decoding Core functions for ADS-B decoding
@@ -181,7 +155,7 @@ Core functions for ADS-B decoding
# Typecode 1-4 # Typecode 1-4
pms.adsb.callsign(msg) pms.adsb.callsign(msg)
# Typecode 5-8 (surface), 9-18 (airborne, barometric height), and 20-22 (airborne, GNSS height) # 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.position(msg_even, msg_odd, t_even, t_odd, lat_ref=None, lon_ref=None)
pms.adsb.airborne_position(msg_even, msg_odd, t_even, t_odd) pms.adsb.airborne_position(msg_even, msg_odd, t_even, t_odd)
pms.adsb.surface_position(msg_even, msg_odd, t_even, t_odd, lat_ref, lon_ref) pms.adsb.surface_position(msg_even, msg_odd, t_even, t_odd, lat_ref, lon_ref)
@@ -275,20 +249,8 @@ Mode-S Enhanced Surveillance (EHS)
pms.commb.vr60ins(msg) # Inertial vertical speed (ft/min) pms.commb.vr60ins(msg) # Inertial vertical speed (ft/min)
Meteorological reports [Experimental] Meteorological routine air report (MRAR) [Experimental]
************************************** ********************************************************
To identify BDS 4,4 and 4,5 codes, you must set ``mrar`` argument to ``True`` in the ``infer()`` function:
.. code:: python
pms.bds.infer(msg. mrar=True)
Once the correct MRAR and MHR messages are identified, decode them as follows:
Meteorological routine air report (MRAR)
+++++++++++++++++++++++++++++++++++++++++
.. code:: python .. code:: python
@@ -299,8 +261,8 @@ Meteorological routine air report (MRAR)
pms.commb.hum44(msg) # Humidity (%) pms.commb.hum44(msg) # Humidity (%)
Meteorological hazard air report (MHR) Meteorological hazard air report (MHR) [Experimental]
+++++++++++++++++++++++++++++++++++++++++ *******************************************************
.. code:: python .. code:: python

View File

@@ -17,5 +17,5 @@ help:
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile %: Makefile
rm -f source/pyModeS*.rst source/modules.rst rm -f source/pyModeS*.rst source/modules.rst
sphinx-apidoc -f -e -M -o source/ ../pyModeS ../pyModeS/decoder/ehs.py ../pyModeS/decoder/els.py ../pyModeS/streamer ../pyModeS/extra sphinx-apidoc -f -e -M -o source/ ../pyModeS
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

View File

@@ -1,17 +0,0 @@
{% extends "!layout.html" %}
{% block footer %}
{{ super() }}
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-74700456-1"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-74700456-1');
</script>
{% endblock %}

View File

@@ -14,20 +14,19 @@
# #
import os import os
import sys import sys
sys.path.insert(0, os.path.abspath('../..'))
sys.path.insert(0, os.path.abspath("../.."))
# -- Project information ----------------------------------------------------- # -- Project information -----------------------------------------------------
project = "pyModeS" project = 'pyModeS'
copyright = "2019, Junzi Sun" copyright = '2019, Junzi Sun'
author = "Junzi Sun" author = 'Junzi Sun'
# The short X.Y version # The short X.Y version
version = "" version = ''
# The full version, including alpha/beta/rc tags # The full version, including alpha/beta/rc tags
release = "" release = ''
# -- General configuration --------------------------------------------------- # -- General configuration ---------------------------------------------------
@@ -40,24 +39,26 @@ release = ""
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones. # ones.
extensions = [ extensions = [
"sphinx.ext.autodoc", 'sphinx.ext.autodoc',
"sphinx.ext.mathjax", 'sphinx.ext.todo',
"sphinx.ext.viewcode", 'sphinx.ext.coverage',
"sphinx.ext.githubpages", 'sphinx.ext.mathjax',
"sphinx.ext.napoleon", 'sphinx.ext.viewcode',
'sphinx.ext.githubpages',
'sphinx.ext.napoleon',
] ]
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"] # templates_path = ['']
# The suffix(es) of source filenames. # The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string: # You can specify multiple suffix as a list of string:
# #
# source_suffix = ['.rst', '.md'] # source_suffix = ['.rst', '.md']
source_suffix = ".rst" source_suffix = '.rst'
# The master toctree document. # The master toctree document.
master_doc = "index" master_doc = 'index'
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.
@@ -81,10 +82,6 @@ pygments_style = None
# a list of builtin themes. # a list of builtin themes.
# #
# html_theme = 'alabaster' # html_theme = 'alabaster'
html_theme = "neo_rtd_theme"
import sphinx_theme
html_theme_path = [sphinx_theme.get_html_theme_path()]
# Theme options are theme-specific and customize the look and feel of a theme # 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 # further. For a list of options available for each theme, see the
@@ -111,7 +108,7 @@ html_theme_path = [sphinx_theme.get_html_theme_path()]
# -- Options for HTMLHelp output --------------------------------------------- # -- Options for HTMLHelp output ---------------------------------------------
# Output file base name for HTML help builder. # Output file base name for HTML help builder.
htmlhelp_basename = "pyModeSdoc" htmlhelp_basename = 'pyModeSdoc'
# -- Options for LaTeX output ------------------------------------------------ # -- Options for LaTeX output ------------------------------------------------
@@ -120,12 +117,15 @@ latex_elements = {
# The paper size ('letterpaper' or 'a4paper'). # The paper size ('letterpaper' or 'a4paper').
# #
# 'papersize': 'letterpaper', # 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt'). # The font size ('10pt', '11pt' or '12pt').
# #
# 'pointsize': '10pt', # 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble. # Additional stuff for the LaTeX preamble.
# #
# 'preamble': '', # 'preamble': '',
# Latex figure (float) alignment # Latex figure (float) alignment
# #
# 'figure_align': 'htbp', # 'figure_align': 'htbp',
@@ -135,7 +135,8 @@ latex_elements = {
# (source start file, target name, title, # (source start file, target name, title,
# author, documentclass [howto, manual, or own class]). # author, documentclass [howto, manual, or own class]).
latex_documents = [ latex_documents = [
(master_doc, "pyModeS.tex", "pyModeS Documentation", "Junzi Sun", "manual") (master_doc, 'pyModeS.tex', 'pyModeS Documentation',
'Junzi Sun', 'manual'),
] ]
@@ -143,7 +144,10 @@ latex_documents = [
# One entry per manual page. List of tuples # One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section). # (source start file, name, description, authors, manual section).
man_pages = [(master_doc, "pymodes", "pyModeS Documentation", [author], 1)] man_pages = [
(master_doc, 'pymodes', 'pyModeS Documentation',
[author], 1)
]
# -- Options for Texinfo output ---------------------------------------------- # -- Options for Texinfo output ----------------------------------------------
@@ -152,15 +156,9 @@ man_pages = [(master_doc, "pymodes", "pyModeS Documentation", [author], 1)]
# (source start file, target name, title, author, # (source start file, target name, title, author,
# dir menu entry, description, category) # dir menu entry, description, category)
texinfo_documents = [ texinfo_documents = [
( (master_doc, 'pyModeS', 'pyModeS Documentation',
master_doc, author, 'pyModeS', 'One line description of project.',
"pyModeS", 'Miscellaneous'),
"pyModeS Documentation",
author,
"pyModeS",
"One line description of project.",
"Miscellaneous",
)
] ]
@@ -179,7 +177,7 @@ epub_title = project
# epub_uid = '' # epub_uid = ''
# A list of files that should not be packed into the epub file. # A list of files that should not be packed into the epub file.
epub_exclude_files = ["search.html"] epub_exclude_files = ['search.html']
# -- Extension configuration ------------------------------------------------- # -- Extension configuration -------------------------------------------------

View File

@@ -9,49 +9,11 @@ Welcome to pyModeS documentation!
The source code can be found at: https://github.com/junzis/pyModeS The source code can be found at: https://github.com/junzis/pyModeS
.. toctree:: .. toctree::
:caption: Core modules :maxdepth: 3
:maxdepth: 2
pyModeS.decoder.adsb
pyModeS.decoder.commb
.. toctree::
:caption: ADS-B messages
:maxdepth: 2
pyModeS.decoder.bds.bds05
pyModeS.decoder.bds.bds06
pyModeS.decoder.bds.bds08
pyModeS.decoder.bds.bds09
.. toctree::
:caption: ELS - elementary surveillance
:maxdepth: 2
pyModeS.decoder.bds.bds10
pyModeS.decoder.bds.bds17
pyModeS.decoder.bds.bds20
pyModeS.decoder.bds.bds30
.. toctree::
:caption: EHS - enhanced surveillance
:maxdepth: 2
pyModeS.decoder.bds.bds40
pyModeS.decoder.bds.bds50
pyModeS.decoder.bds.bds60
.. toctree::
:caption: MRAR / MHR
:maxdepth: 2
pyModeS.decoder.bds.bds44
pyModeS.decoder.bds.bds45
pyModeS.decoder
pyModeS.streamer
pyModeS.extra
---- ----

35
doc/source/make.bat Normal file
View 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

View File

@@ -1,7 +0,0 @@
pyModeS.c\_common module
========================
.. automodule:: pyModeS.c_common
:members:
:undoc-members:
:show-inheritance:

View File

@@ -1,7 +0,0 @@
pyModeS.common module
=====================
.. automodule:: pyModeS.common
:members:
:undoc-members:
:show-inheritance:

View File

@@ -2,6 +2,6 @@ pyModeS.decoder.acas module
=========================== ===========================
.. automodule:: pyModeS.decoder.acas .. automodule:: pyModeS.decoder.acas
:members: :members:
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:

View File

@@ -2,6 +2,6 @@ pyModeS.decoder.adsb module
=========================== ===========================
.. automodule:: pyModeS.decoder.adsb .. automodule:: pyModeS.decoder.adsb
:members: :members:
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:

View File

@@ -2,6 +2,6 @@ pyModeS.decoder.allcall module
============================== ==============================
.. automodule:: pyModeS.decoder.allcall .. automodule:: pyModeS.decoder.allcall
:members: :members:
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:

View File

@@ -2,6 +2,6 @@ pyModeS.decoder.bds.bds05 module
================================ ================================
.. automodule:: pyModeS.decoder.bds.bds05 .. automodule:: pyModeS.decoder.bds.bds05
:members: :members:
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:

View File

@@ -2,6 +2,6 @@ pyModeS.decoder.bds.bds06 module
================================ ================================
.. automodule:: pyModeS.decoder.bds.bds06 .. automodule:: pyModeS.decoder.bds.bds06
:members: :members:
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:

View File

@@ -2,6 +2,6 @@ pyModeS.decoder.bds.bds08 module
================================ ================================
.. automodule:: pyModeS.decoder.bds.bds08 .. automodule:: pyModeS.decoder.bds.bds08
:members: :members:
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:

View File

@@ -2,6 +2,6 @@ pyModeS.decoder.bds.bds09 module
================================ ================================
.. automodule:: pyModeS.decoder.bds.bds09 .. automodule:: pyModeS.decoder.bds.bds09
:members: :members:
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:

View File

@@ -2,6 +2,6 @@ pyModeS.decoder.bds.bds10 module
================================ ================================
.. automodule:: pyModeS.decoder.bds.bds10 .. automodule:: pyModeS.decoder.bds.bds10
:members: :members:
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:

View File

@@ -2,6 +2,6 @@ pyModeS.decoder.bds.bds17 module
================================ ================================
.. automodule:: pyModeS.decoder.bds.bds17 .. automodule:: pyModeS.decoder.bds.bds17
:members: :members:
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:

View File

@@ -2,6 +2,6 @@ pyModeS.decoder.bds.bds20 module
================================ ================================
.. automodule:: pyModeS.decoder.bds.bds20 .. automodule:: pyModeS.decoder.bds.bds20
:members: :members:
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:

View File

@@ -2,6 +2,6 @@ pyModeS.decoder.bds.bds30 module
================================ ================================
.. automodule:: pyModeS.decoder.bds.bds30 .. automodule:: pyModeS.decoder.bds.bds30
:members: :members:
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:

View File

@@ -2,6 +2,6 @@ pyModeS.decoder.bds.bds40 module
================================ ================================
.. automodule:: pyModeS.decoder.bds.bds40 .. automodule:: pyModeS.decoder.bds.bds40
:members: :members:
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:

View File

@@ -2,6 +2,6 @@ pyModeS.decoder.bds.bds44 module
================================ ================================
.. automodule:: pyModeS.decoder.bds.bds44 .. automodule:: pyModeS.decoder.bds.bds44
:members: :members:
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:

View File

@@ -2,6 +2,6 @@ pyModeS.decoder.bds.bds45 module
================================ ================================
.. automodule:: pyModeS.decoder.bds.bds45 .. automodule:: pyModeS.decoder.bds.bds45
:members: :members:
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:

View File

@@ -2,6 +2,6 @@ pyModeS.decoder.bds.bds50 module
================================ ================================
.. automodule:: pyModeS.decoder.bds.bds50 .. automodule:: pyModeS.decoder.bds.bds50
:members: :members:
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:

View File

@@ -2,6 +2,6 @@ pyModeS.decoder.bds.bds53 module
================================ ================================
.. automodule:: pyModeS.decoder.bds.bds53 .. automodule:: pyModeS.decoder.bds.bds53
:members: :members:
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:

View File

@@ -2,6 +2,6 @@ pyModeS.decoder.bds.bds60 module
================================ ================================
.. automodule:: pyModeS.decoder.bds.bds60 .. automodule:: pyModeS.decoder.bds.bds60
:members: :members:
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:

View File

@@ -2,15 +2,14 @@ pyModeS.decoder.bds package
=========================== ===========================
.. automodule:: pyModeS.decoder.bds .. automodule:: pyModeS.decoder.bds
:members: :members:
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
Submodules Submodules
---------- ----------
.. toctree:: .. toctree::
:maxdepth: 4
pyModeS.decoder.bds.bds05 pyModeS.decoder.bds.bds05
pyModeS.decoder.bds.bds06 pyModeS.decoder.bds.bds06
@@ -26,3 +25,4 @@ Submodules
pyModeS.decoder.bds.bds50 pyModeS.decoder.bds.bds50
pyModeS.decoder.bds.bds53 pyModeS.decoder.bds.bds53
pyModeS.decoder.bds.bds60 pyModeS.decoder.bds.bds60

View File

@@ -2,6 +2,6 @@ pyModeS.decoder.commb module
============================ ============================
.. automodule:: pyModeS.decoder.commb .. automodule:: pyModeS.decoder.commb
:members: :members:
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:

View File

@@ -0,0 +1,7 @@
pyModeS.decoder.common module
=============================
.. automodule:: pyModeS.decoder.common
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
pyModeS.decoder.ehs module
==========================
.. automodule:: pyModeS.decoder.ehs
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
pyModeS.decoder.els module
==========================
.. automodule:: pyModeS.decoder.els
:members:
:undoc-members:
:show-inheritance:

View File

@@ -2,28 +2,29 @@ pyModeS.decoder package
======================= =======================
.. automodule:: pyModeS.decoder .. automodule:: pyModeS.decoder
:members: :members:
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
Subpackages Subpackages
----------- -----------
.. toctree:: .. toctree::
:maxdepth: 4
pyModeS.decoder.bds pyModeS.decoder.bds
Submodules Submodules
---------- ----------
.. toctree:: .. toctree::
:maxdepth: 4
pyModeS.decoder.acas pyModeS.decoder.acas
pyModeS.decoder.adsb pyModeS.decoder.adsb
pyModeS.decoder.allcall pyModeS.decoder.allcall
pyModeS.decoder.commb pyModeS.decoder.commb
pyModeS.decoder.common
pyModeS.decoder.ehs
pyModeS.decoder.els
pyModeS.decoder.surv pyModeS.decoder.surv
pyModeS.decoder.uncertainty pyModeS.decoder.uncertainty
pyModeS.decoder.uplink

View File

@@ -2,6 +2,6 @@ pyModeS.decoder.surv module
=========================== ===========================
.. automodule:: pyModeS.decoder.surv .. automodule:: pyModeS.decoder.surv
:members: :members:
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:

View File

@@ -2,6 +2,6 @@ pyModeS.decoder.uncertainty module
================================== ==================================
.. automodule:: pyModeS.decoder.uncertainty .. automodule:: pyModeS.decoder.uncertainty
:members: :members:
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:

View File

@@ -1,7 +0,0 @@
pyModeS.decoder.uplink module
=============================
.. automodule:: pyModeS.decoder.uplink
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
pyModeS.extra.aero module
=========================
.. automodule:: pyModeS.extra.aero
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,16 @@
pyModeS.extra package
=====================
.. automodule:: pyModeS.extra
:members:
:undoc-members:
:show-inheritance:
Submodules
----------
.. toctree::
pyModeS.extra.aero
pyModeS.extra.tcpclient

View File

@@ -0,0 +1,7 @@
pyModeS.extra.tcpclient module
==============================
.. automodule:: pyModeS.extra.tcpclient
:members:
:undoc-members:
:show-inheritance:

View File

@@ -2,23 +2,16 @@ pyModeS package
=============== ===============
.. automodule:: pyModeS .. automodule:: pyModeS
:members: :members:
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
Subpackages Subpackages
----------- -----------
.. toctree:: .. toctree::
:maxdepth: 4
pyModeS.decoder pyModeS.decoder
pyModeS.extra
pyModeS.streamer
Submodules
----------
.. toctree::
:maxdepth: 4
pyModeS.c_common
pyModeS.common

View File

@@ -0,0 +1,16 @@
pyModeS.streamer package
========================
.. automodule:: pyModeS.streamer
:members:
:undoc-members:
:show-inheritance:
Submodules
----------
.. toctree::
pyModeS.streamer.screen
pyModeS.streamer.stream

View File

@@ -0,0 +1,7 @@
pyModeS.streamer.screen module
==============================
.. automodule:: pyModeS.streamer.screen
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,7 @@
pyModeS.streamer.stream module
==============================
.. automodule:: pyModeS.streamer.stream
:members:
:undoc-members:
:show-inheritance:

View File

View File

@@ -1,23 +1,22 @@
from __future__ import absolute_import, print_function, division
import os import os
import warnings import warnings
try: try:
from . import c_common as common from .decoder import c_common as common
from .c_common import * from .decoder.c_common import *
except: except:
from . import py_common as common from .decoder import common
from .py_common import * from .decoder.common import *
from .decoder import tell from .decoder import tell
from .decoder import adsb from .decoder import adsb
from .decoder import commb from .decoder import commb
from .decoder import allcall
from .decoder import surv
from .decoder import bds from .decoder import bds
from .extra import aero from .extra import aero
from .extra import tcpclient from .extra import tcpclient
warnings.simplefilter("once", DeprecationWarning) warnings.simplefilter("once", DeprecationWarning)
dirpath = os.path.dirname(os.path.realpath(__file__)) dirpath = os.path.dirname(os.path.realpath(__file__))

View File

@@ -1,6 +1,9 @@
def tell(msg: str) -> None: from __future__ import absolute_import, print_function, division
from pyModeS import common, adsb, commb, bds
from pyModeS.decoder import adsb, commb, common, bds
def tell(msg):
def _print(label, value, unit=None): def _print(label, value, unit=None):
print("%20s: " % label, end="") print("%20s: " % label, end="")
print("%s " % value, end="") print("%s " % value, end="")
@@ -17,7 +20,7 @@ def tell(msg: str) -> None:
_print("Downlink Format", df) _print("Downlink Format", df)
if df == 17: if df == 17:
_print("Protocol", "Mode-S Extended Squitter (ADS-B)") _print("Protocal", "Mode-S Extended Squitter (ADS-B)")
tc = common.typecode(msg) tc = common.typecode(msg)
if 1 <= tc <= 4: # callsign if 1 <= tc <= 4: # callsign
@@ -26,7 +29,7 @@ def tell(msg: str) -> None:
_print("Callsign:", callsign) _print("Callsign:", callsign)
if 5 <= tc <= 8: # surface position if 5 <= tc <= 8: # surface position
_print("Type", "Surface position") _print("Type", "Surface postition")
oe = adsb.oe_flag(msg) oe = adsb.oe_flag(msg)
msgbin = common.hex2bin(msg) msgbin = common.hex2bin(msg)
cprlat = common.bin2int(msgbin[54:71]) / 131072.0 cprlat = common.bin2int(msgbin[54:71]) / 131072.0
@@ -72,11 +75,11 @@ def tell(msg: str) -> None:
_print("Altitude", alt, "feet") _print("Altitude", alt, "feet")
if df == 20: if df == 20:
_print("Protocol", "Mode-S Comm-B altitude reply") _print("Protocal", "Mode-S Comm-B altitude reply")
_print("Altitude", common.altcode(msg), "feet") _print("Altitude", common.altcode(msg), "feet")
if df == 21: if df == 21:
_print("Protocol", "Mode-S Comm-B identity reply") _print("Protocal", "Mode-S Comm-B identity reply")
_print("Squawk code", common.idcode(msg)) _print("Squawk code", common.idcode(msg))
if df == 20 or df == 21: if df == 20 or df == 21:

View File

@@ -3,3 +3,6 @@ Decoding Air-Air Surveillance (ACAS) DF=0/16
[To be implemented] [To be implemented]
""" """
from __future__ import absolute_import, print_function, division
from pyModeS.decoder import common

View File

@@ -1,18 +1,22 @@
"""ADS-B module. """ADS-B Wrapper.
The ADS-B module also imports functions from the following modules: The ADS-B wrapper also imports functions from the following modules:
- pyModeS.decoder.bds.bds05: ``airborne_position()``, ``airborne_position_with_ref()``, ``altitude()`` - pyModeS.decoder.bds.bds05
- pyModeS.decoder.bds.bds06: ``surface_position()``, ``surface_position_with_ref()``, ``surface_velocity()`` Functions: ``airborne_position``, ``airborne_position_with_ref``, ``altitude``
- pyModeS.decoder.bds.bds08: ``category()``, ``callsign()`` - pyModeS.decoder.bds.bds06
- pyModeS.decoder.bds.bds09: ``airborne_velocity()``, ``altitude_diff()`` 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 import pyModeS as pms
from pyModeS.decoder import common
from pyModeS import common
from pyModeS.decoder import uncertainty from pyModeS.decoder import uncertainty
# from pyModeS.decoder.bds import bds05, bds06, bds09 # from pyModeS.decoder.bds import bds05, bds06, bds09
@@ -28,7 +32,6 @@ from pyModeS.decoder.bds.bds06 import (
) )
from pyModeS.decoder.bds.bds08 import category, callsign from pyModeS.decoder.bds.bds08 import category, callsign
from pyModeS.decoder.bds.bds09 import airborne_velocity, altitude_diff from pyModeS.decoder.bds.bds09 import airborne_velocity, altitude_diff
from pyModeS.decoder.bds.bds61 import is_emergency, emergency_state, emergency_squawk
def df(msg): def df(msg):
@@ -44,32 +47,27 @@ def typecode(msg):
def position(msg0, msg1, t0, t1, lat_ref=None, lon_ref=None): def position(msg0, msg1, t0, t1, lat_ref=None, lon_ref=None):
"""Decode surface or airborne position from a pair of even and odd """Decode position from a pair of even and odd position message
position messages. (works with both airborne and surface position messages)
Note, that to decode surface position using the position message pair,
the reference position has to be provided.
Args: Args:
msg0 (string): even message (28 hexdigits) msg0 (string): even message (28 bytes hexadecimal string)
msg1 (string): odd message (28 hexdigits) msg1 (string): odd message (28 bytes hexadecimal string)
t0 (int): timestamps for the even message t0 (int): timestamps for the even message
t1 (int): timestamps for the odd message t1 (int): timestamps for the odd message
lat_ref (float): latitude of reference position
lon_ref (float): longitude of reference position
Returns: Returns:
(float, float): (latitude, longitude) of the aircraft (float, float): (latitude, longitude) of the aircraft
""" """
tc0 = typecode(msg0) tc0 = typecode(msg0)
tc1 = typecode(msg1) tc1 = typecode(msg1)
if 5 <= tc0 <= 8 and 5 <= tc1 <= 8: if 5 <= tc0 <= 8 and 5 <= tc1 <= 8:
if lat_ref is None or lon_ref is None: if (not lat_ref) or (not lon_ref):
raise RuntimeError( raise RuntimeError(
"Surface position encountered, a reference position" "Surface position encountered, a reference \
" lat/lon required. Location of receiver can be used." position lat/lon required. Location of \
receiver can be used."
) )
else: else:
return surface_position(msg0, msg1, t0, t1, lat_ref, lon_ref) return surface_position(msg0, msg1, t0, t1, lat_ref, lon_ref)
@@ -83,20 +81,19 @@ def position(msg0, msg1, t0, t1, lat_ref=None, lon_ref=None):
return airborne_position(msg0, msg1, t0, t1) return airborne_position(msg0, msg1, t0, t1)
else: else:
raise RuntimeError("Incorrect or inconsistent message types") raise RuntimeError("incorrect or inconsistant message types")
def position_with_ref(msg, lat_ref, lon_ref): def position_with_ref(msg, lat_ref, lon_ref):
"""Decode position with only one message. """Decode position with only one message,
knowing reference nearby location, such as previously
A reference position is required, which can be previously calculated location, ground station, or airport location, etc.
calculated location, ground station, or airport location. Works with both airborne and surface position messages.
The function works with both airborne and surface position messages.
The reference position shall be with in 180NM (airborne) or 45NM (surface) The reference position shall be with in 180NM (airborne) or 45NM (surface)
of the true position. of the true position.
Args: Args:
msg (str): even message (28 hexdigits) msg (string): even message (28 bytes hexadecimal string)
lat_ref: previous known latitude lat_ref: previous known latitude
lon_ref: previous known longitude lon_ref: previous known longitude
@@ -113,19 +110,19 @@ def position_with_ref(msg, lat_ref, lon_ref):
return airborne_position_with_ref(msg, lat_ref, lon_ref) return airborne_position_with_ref(msg, lat_ref, lon_ref)
else: else:
raise RuntimeError("incorrect or inconsistent message types") raise RuntimeError("incorrect or inconsistant message types")
def altitude(msg): def altitude(msg):
"""Decode aircraft altitude. """Decode aircraft altitude
Args: Args:
msg (str): 28 hexdigits string msg (string): 28 bytes hexadecimal message string
Returns: Returns:
int: altitude in feet int: altitude in feet
""" """
tc = typecode(msg) tc = typecode(msg)
if tc < 5 or tc == 19 or tc > 22: if tc < 5 or tc == 19 or tc > 22:
@@ -140,35 +137,40 @@ def altitude(msg):
return altitude05(msg) return altitude05(msg)
def velocity(msg, source=False): def velocity(msg, rtn_sources=False):
"""Calculate the speed, heading, and vertical rate (handles both airborne or surface message). """Calculate the speed, heading, and vertical rate
(handles both airborne or surface message)
Args: Args:
msg (str): 28 hexdigits string msg (string): 28 bytes hexadecimal message string
source (boolean): Include direction and vertical rate sources in return. Default to False. rtn_source (boolean): If the function will return
If set to True, the function will return six value instead of four. 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: Returns:
int, float, int, string, [string], [string]: Four or six parameters, including: (int, float, int, string, string, string): speed (kt),
- Speed (kt) ground track or heading (degree),
- Angle (degree), either ground track or heading rate of climb/descent (ft/min), speed type
- Vertical rate (ft/min) ('GS' for ground speed, 'AS' for airspeed),
- Speed type ('GS' for ground speed, 'AS' for airspeed) direction source ('true_north' for ground track / true north
- [Optional] Direction source ('TRUE_NORTH' or 'MAGENTIC_NORTH') as refrence, 'mag_north' for magnetic north as reference),
- [Optional] Vertical rate source ('BARO' or 'GNSS') rate of climb/descent source ('Baro' for barometer, 'GNSS'
for GNSS constellation).
For surface messages, vertical rate and its respective sources are set to None.
In the case of surface messages, None will be put in place
for vertical rate and its respective sources.
""" """
if 5 <= typecode(msg) <= 8: if 5 <= typecode(msg) <= 8:
return surface_velocity(msg, source) return surface_velocity(msg, rtn_sources)
elif typecode(msg) == 19: elif typecode(msg) == 19:
return airborne_velocity(msg, source) return airborne_velocity(msg, rtn_sources)
else: else:
raise RuntimeError( raise RuntimeError(
"incorrect or inconsistent message types, expecting 4<TC<9 or TC=19" "incorrect or inconsistant message types, expecting 4<TC<9 or TC=19"
) )
@@ -177,7 +179,7 @@ def speed_heading(msg):
(handles both airborne or surface message) (handles both airborne or surface message)
Args: Args:
msg (str): 28 hexdigits string msg (string): 28 bytes hexadecimal message string
Returns: Returns:
(int, float): speed (kt), ground track or heading (degree) (int, float): speed (kt), ground track or heading (degree)
@@ -189,7 +191,7 @@ def speed_heading(msg):
def oe_flag(msg): def oe_flag(msg):
"""Check the odd/even flag. Bit 54, 0 for even, 1 for odd. """Check the odd/even flag. Bit 54, 0 for even, 1 for odd.
Args: Args:
msg (str): 28 hexdigits string msg (string): 28 bytes hexadecimal message string
Returns: Returns:
int: 0 or 1, for even or odd frame int: 0 or 1, for even or odd frame
""" """
@@ -201,7 +203,7 @@ def version(msg):
"""ADS-B Version """ADS-B Version
Args: Args:
msg (str): 28 hexdigits string, TC = 31 msg (string): 28 bytes hexadecimal message string, TC = 31
Returns: Returns:
int: version number int: version number
@@ -223,7 +225,7 @@ def nuc_p(msg):
"""Calculate NUCp, Navigation Uncertainty Category - Position (ADS-B version 1) """Calculate NUCp, Navigation Uncertainty Category - Position (ADS-B version 1)
Args: Args:
msg (str): 28 hexdigits string, msg (string): 28 bytes hexadecimal message string,
Returns: Returns:
int: Horizontal Protection Limit int: Horizontal Protection Limit
@@ -259,7 +261,7 @@ def nuc_v(msg):
"""Calculate NUCv, Navigation Uncertainty Category - Velocity (ADS-B version 1) """Calculate NUCv, Navigation Uncertainty Category - Velocity (ADS-B version 1)
Args: Args:
msg (str): 28 hexdigits string, msg (string): 28 bytes hexadecimal message string,
Returns: Returns:
int or string: 95% Horizontal Velocity Error int or string: 95% Horizontal Velocity Error
@@ -288,7 +290,7 @@ def nic_v1(msg, NICs):
"""Calculate NIC, navigation integrity category, for ADS-B version 1 """Calculate NIC, navigation integrity category, for ADS-B version 1
Args: Args:
msg (str): 28 hexdigits string msg (string): 28 bytes hexadecimal message string
NICs (int or string): NIC supplement NICs (int or string): NIC supplement
Returns: Returns:
@@ -322,7 +324,7 @@ def nic_v2(msg, NICa, NICbc):
"""Calculate NIC, navigation integrity category, for ADS-B version 2 """Calculate NIC, navigation integrity category, for ADS-B version 2
Args: Args:
msg (str): 28 hexdigits string msg (string): 28 bytes hexadecimal message string
NICa (int or string): NIC supplement - A NICa (int or string): NIC supplement - A
NICbc (int or srting): NIC supplement - B or C NICbc (int or srting): NIC supplement - B or C
@@ -360,7 +362,7 @@ def nic_s(msg):
"""Obtain NIC supplement bit, TC=31 message """Obtain NIC supplement bit, TC=31 message
Args: Args:
msg (str): 28 hexdigits string msg (string): 28 bytes hexadecimal message string
Returns: Returns:
int: NICs number (0 or 1) int: NICs number (0 or 1)
@@ -382,7 +384,7 @@ def nic_a_c(msg):
"""Obtain NICa/c, navigation integrity category supplements a and c """Obtain NICa/c, navigation integrity category supplements a and c
Args: Args:
msg (str): 28 hexdigits string msg (string): 28 bytes hexadecimal message string
Returns: Returns:
(int, int): NICa and NICc number (0 or 1) (int, int): NICa and NICc number (0 or 1)
@@ -405,7 +407,7 @@ def nic_b(msg):
"""Obtain NICb, navigation integrity category supplement-b """Obtain NICb, navigation integrity category supplement-b
Args: Args:
msg (str): 28 hexdigits string msg (string): 28 bytes hexadecimal message string
Returns: Returns:
int: NICb number (0 or 1) int: NICb number (0 or 1)
@@ -427,7 +429,7 @@ def nac_p(msg):
"""Calculate NACp, Navigation Accuracy Category - Position """Calculate NACp, Navigation Accuracy Category - Position
Args: Args:
msg (str): 28 hexdigits string, TC = 29 or 31 msg (string): 28 bytes hexadecimal message string, TC = 29 or 31
Returns: Returns:
int or string: 95% horizontal accuracy bounds, Estimated Position Uncertainty int or string: 95% horizontal accuracy bounds, Estimated Position Uncertainty
@@ -462,7 +464,7 @@ def nac_v(msg):
"""Calculate NACv, Navigation Accuracy Category - Velocity """Calculate NACv, Navigation Accuracy Category - Velocity
Args: Args:
msg (str): 28 hexdigits string, TC = 19 msg (string): 28 bytes hexadecimal message string, TC = 19
Returns: Returns:
int or string: 95% horizontal accuracy bounds for velocity, Horizontal Figure of Merit int or string: 95% horizontal accuracy bounds for velocity, Horizontal Figure of Merit
@@ -491,7 +493,7 @@ def sil(msg, version):
"""Calculate SIL, Surveillance Integrity Level """Calculate SIL, Surveillance Integrity Level
Args: Args:
msg (str): 28 hexdigits string with TC = 29, 31 msg (string): 28 bytes hexadecimal message string with TC = 29, 31
Returns: Returns:
int or string: Probability of exceeding Horizontal Radius of Containment RCu int or string: Probability of exceeding Horizontal Radius of Containment RCu
@@ -502,7 +504,7 @@ def sil(msg, version):
if tc not in [29, 31]: if tc not in [29, 31]:
raise RuntimeError( raise RuntimeError(
"%s: Not a target state and status message, \ "%s: Not a target state and status messag, \
or operation status message, expecting TC = 29 or 31" or operation status message, expecting TC = 29 or 31"
% msg % msg
) )

View File

@@ -1,82 +1,8 @@
""" """
Decode all-call reply messages, with dowlink format 11 Decoding all call replies DF=11
[To be implemented]
""" """
from pyModeS import common from __future__ import absolute_import, print_function, division
from pyModeS.decoder import common
def _checkdf(func):
"""Ensure downlink format is 11."""
def wrapper(msg):
df = common.df(msg)
if df != 11:
raise RuntimeError(
"Incorrect downlink format, expect 11, got {}".format(df)
)
return func(msg)
return wrapper
@_checkdf
def icao(msg):
"""Decode transponder code (ICAO address).
Args:
msg (str): 14 hexdigits string
Returns:
string: ICAO address
"""
return common.icao(msg)
@_checkdf
def interrogator(msg):
"""Decode interrogator identifier code.
Args:
msg (str): 14 hexdigits string
Returns:
int: interrogator identifier code
"""
# the CRC remainder contains the CL and IC field. top three bits are CL field and last four bits are IC field.
remainder = common.crc(msg)
if remainder > 79:
IC = "corrupt IC"
elif remainder < 16:
IC="II"+str(remainder)
else:
IC="SI"+str(remainder-16)
return IC
@_checkdf
def capability(msg):
"""Decode transponder capability.
Args:
msg (str): 14 hexdigits string
Returns:
int, str: transponder capability, description
"""
msgbin = common.hex2bin(msg)
ca = common.bin2int(msgbin[5:8])
if ca == 0:
text = "level 1 transponder"
elif ca == 4:
text = "level 2 transponder, ability to set CA to 7, on ground"
elif ca == 5:
text = "level 2 transponder, ability to set CA to 7, airborne"
elif ca == 6:
text = "evel 2 transponder, ability to set CA to 7, either airborne or ground"
elif ca == 7:
text = "Downlink Request value is 0,or the Flight Status is 2, 3, 4 or 5, either airborne or on the ground"
else:
text = None
return ca, text

View File

@@ -18,11 +18,11 @@
Common functions for Mode-S decoding Common functions for Mode-S decoding
""" """
from __future__ import absolute_import, print_function, division
import numpy as np import numpy as np
from pyModeS.extra import aero from pyModeS.extra import aero
from pyModeS import common from pyModeS.decoder import common
from pyModeS.decoder.bds import ( from pyModeS.decoder.bds import (
bds05, bds05,
bds06, bds06,
@@ -45,7 +45,7 @@ def is50or60(msg, spd_ref, trk_ref, alt_ref):
"""Use reference ground speed and trk to determine BDS50 and DBS60. """Use reference ground speed and trk to determine BDS50 and DBS60.
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message string
spd_ref (float): reference speed (ADS-B ground speed), kts spd_ref (float): reference speed (ADS-B ground speed), kts
trk_ref (float): reference track (ADS-B track angle), deg trk_ref (float): reference track (ADS-B track angle), deg
alt_ref (float): reference altitude (ADS-B altitude), ft alt_ref (float): reference altitude (ADS-B altitude), ft
@@ -60,34 +60,25 @@ def is50or60(msg, spd_ref, trk_ref, alt_ref):
vy = v * np.cos(np.radians(angle)) vy = v * np.cos(np.radians(angle))
return vx, vy return vx, vy
# message must be both BDS 50 and 60 before processing
if not (bds50.is50(msg) and bds60.is60(msg)): if not (bds50.is50(msg) and bds60.is60(msg)):
return None return None
# --- assuming BDS60 --- h50 = bds50.trk50(msg)
v50 = bds50.gs50(msg)
if h50 is None or v50 is None:
return "BDS50,BDS60"
h60 = bds60.hdg60(msg) h60 = bds60.hdg60(msg)
m60 = bds60.mach60(msg) m60 = bds60.mach60(msg)
i60 = bds60.ias60(msg) i60 = bds60.ias60(msg)
# additional check now knowing the altitude
if (m60 is not None) and (i60 is not None):
ias_ = aero.mach2cas(m60, alt_ref * aero.ft) / aero.kts
if abs(i60 - ias_) > 20:
return "BDS50"
if h60 is None or (m60 is None and i60 is None): if h60 is None or (m60 is None and i60 is None):
return "BDS50,BDS60" return "BDS50,BDS60"
m60 = np.nan if m60 is None else m60 m60 = np.nan if m60 is None else m60
i60 = np.nan if i60 is None else i60 i60 = np.nan if i60 is None else i60
# --- assuming BDS50 ---
h50 = bds50.trk50(msg)
v50 = bds50.gs50(msg)
if h50 is None or v50 is None:
return "BDS50,BDS60"
XY5 = vxy(v50 * aero.kts, h50) XY5 = vxy(v50 * aero.kts, h50)
XY6m = vxy(aero.mach2tas(m60, alt_ref * aero.ft), h60) XY6m = vxy(aero.mach2tas(m60, alt_ref * aero.ft), h60)
XY6i = vxy(aero.cas2tas(i60 * aero.kts, alt_ref * aero.ft), h60) XY6i = vxy(aero.cas2tas(i60 * aero.kts, alt_ref * aero.ft), h60)
@@ -117,7 +108,7 @@ def infer(msg, mrar=False):
"""Estimate the most likely BDS code of an message. """Estimate the most likely BDS code of an message.
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message string
mrar (bool): Also infer MRAR (BDS 44) and MHR (BDS 45). Defaults to False. mrar (bool): Also infer MRAR (BDS 44) and MHR (BDS 45). Defaults to False.
Returns: Returns:
@@ -134,7 +125,7 @@ def infer(msg, mrar=False):
tc = common.typecode(msg) tc = common.typecode(msg)
if 1 <= tc <= 4: if 1 <= tc <= 4:
return "BDS08" # identification and category return "BDS08" # indentification and category
if 5 <= tc <= 8: if 5 <= tc <= 8:
return "BDS06" # surface movement return "BDS06" # surface movement
if 9 <= tc <= 18: if 9 <= tc <= 18:

View File

@@ -4,15 +4,17 @@
# Airborn position # Airborn position
# ------------------------------------------ # ------------------------------------------
from pyModeS import common
from __future__ import absolute_import, print_function, division
from pyModeS.decoder import common
def airborne_position(msg0, msg1, t0, t1): def airborne_position(msg0, msg1, t0, t1):
"""Decode airborn position from a pair of even and odd position message """Decode airborn position from a pair of even and odd position message
Args: Args:
msg0 (string): even message (28 hexdigits) msg0 (string): even message (28 bytes hexadecimal string)
msg1 (string): odd message (28 hexdigits) msg1 (string): odd message (28 bytes hexadecimal string)
t0 (int): timestamps for the even message t0 (int): timestamps for the even message
t1 (int): timestamps for the odd message t1 (int): timestamps for the odd message
@@ -85,7 +87,7 @@ def airborne_position_with_ref(msg, lat_ref, lon_ref):
be with in 180NM of the true position. be with in 180NM of the true position.
Args: Args:
msg (str): even message (28 hexdigits) msg (string): even message (28 bytes hexadecimal string)
lat_ref: previous known latitude lat_ref: previous known latitude
lon_ref: previous known longitude lon_ref: previous known longitude
@@ -127,7 +129,7 @@ def altitude(msg):
"""Decode aircraft altitude """Decode aircraft altitude
Args: Args:
msg (str): 28 hexdigits string msg (string): 28 bytes hexadecimal message string
Returns: Returns:
int: altitude in feet int: altitude in feet
@@ -139,13 +141,17 @@ def altitude(msg):
raise RuntimeError("%s: Not a airborn position message" % msg) raise RuntimeError("%s: Not a airborn position message" % msg)
mb = common.hex2bin(msg)[32:] mb = common.hex2bin(msg)[32:]
altbin = mb[8:20]
if tc < 19: if tc < 19:
altcode = altbin[0:6] + "0" + altbin[6:] # barometric altitude
q = mb[15]
if q:
n = common.bin2int(mb[8:15] + mb[16:20])
alt = n * 25 - 1000
else:
alt = None
else: else:
altcode = altbin[0:6] + "0" + altbin[6:] # GNSS altitude, meters -> feet
alt = common.bin2int(mb[8:20]) * 3.28084
alt = common.altitude(altcode)
return alt return alt

View File

@@ -1,10 +1,12 @@
# ------------------------------------------ # ------------------------------------------
# BDS 0,6 # BDS 0,6
# ADS-B TC=5-8 # ADS-B TC=5-8
# Surface movment # Surface position
# ------------------------------------------ # ------------------------------------------
from pyModeS import common from __future__ import absolute_import, print_function, division
from pyModeS.decoder import common
import math
def surface_position(msg0, msg1, t0, t1, lat_ref, lon_ref): def surface_position(msg0, msg1, t0, t1, lat_ref, lon_ref):
@@ -12,8 +14,8 @@ def surface_position(msg0, msg1, t0, t1, lat_ref, lon_ref):
the lat/lon of receiver must be provided to yield the correct solution. the lat/lon of receiver must be provided to yield the correct solution.
Args: Args:
msg0 (string): even message (28 hexdigits) msg0 (string): even message (28 bytes hexadecimal string)
msg1 (string): odd message (28 hexdigits) msg1 (string): odd message (28 bytes hexadecimal string)
t0 (int): timestamps for the even message t0 (int): timestamps for the even message
t1 (int): timestamps for the odd message t1 (int): timestamps for the odd message
lat_ref (float): latitude of the receiver lat_ref (float): latitude of the receiver
@@ -89,7 +91,7 @@ def surface_position_with_ref(msg, lat_ref, lon_ref):
be with in 45NM of the true position. be with in 45NM of the true position.
Args: Args:
msg (str): even message (28 hexdigits) msg (string): even message (28 bytes hexadecimal string)
lat_ref: previous known latitude lat_ref: previous known latitude
lon_ref: previous known longitude lon_ref: previous known longitude
@@ -127,24 +129,23 @@ def surface_position_with_ref(msg, lat_ref, lon_ref):
return round(lat, 5), round(lon, 5) return round(lat, 5), round(lon, 5)
def surface_velocity(msg, source=False): def surface_velocity(msg, rtn_sources=False):
"""Decode surface velocity from a surface position message """Decode surface velocity from from a surface position message
Args: Args:
msg (str): 28 hexdigits string msg (string): 28 bytes hexadecimal message string
source (boolean): Include direction and vertical rate sources in return. Default to False. rtn_source (boolean): If the function will return
If set to True, the function will return six value instead of four. 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: Returns:
int, float, int, string, [string], [string]: Four or six parameters, including: (int, float, int, string, string, None): speed (kt),
- Speed (kt) ground track (degree), None for rate of climb/descend (ft/min),
- Angle (degree), ground track and speed type ('GS' for ground speed), direction source
- Vertical rate, always 0 ('true_north' for ground track / true north as reference),
- Speed type ('GS' for ground speed, 'AS' for airspeed) None rate of climb/descent source.
- [Optional] Direction source ('TRUE_NORTH')
- [Optional] Vertical rate source (None)
""" """
if common.typecode(msg) < 5 or common.typecode(msg) > 8: if common.typecode(msg) < 5 or common.typecode(msg) > 8:
raise RuntimeError("%s: Not a surface message, expecting 5<TC<8" % msg) raise RuntimeError("%s: Not a surface message, expecting 5<TC<8" % msg)
@@ -158,7 +159,7 @@ def surface_velocity(msg, source=False):
else: else:
trk = None trk = None
# ground movement / speed # ground movment / speed
mov = common.bin2int(mb[5:12]) mov = common.bin2int(mb[5:12])
if mov == 0 or mov > 124: if mov == 0 or mov > 124:
@@ -168,12 +169,14 @@ def surface_velocity(msg, source=False):
elif mov == 124: elif mov == 124:
spd = 175 spd = 175
else: else:
mov_lb = [2, 9, 13, 39, 94, 109, 124] movs = [2, 9, 13, 39, 94, 109, 124]
kts_lb = [0.125, 1, 2, 15, 70, 100, 175] kts = [0.125, 1, 2, 15, 70, 100, 175]
step = [0.125, 0.25, 0.5, 1, 2, 5] i = next(m[0] for m in enumerate(movs) if m[1] > mov)
i = next(m[0] for m in enumerate(mov_lb) if m[1] > mov) step = (kts[i] - kts[i - 1]) * 1.0 / (movs[i] - movs[i - 1])
spd = kts_lb[i - 1] + (mov - mov_lb[i - 1]) * step[i - 1] spd = kts[i - 1] + (mov - movs[i - 1]) * step
if source: spd = round(spd, 2)
return spd, trk, 0, "GS", "TRUE_NORTH", None
if rtn_sources:
return spd, trk, 0, "GS", "true_north", None
else: else:
return spd, trk, 0, "GS" return spd, trk, 0, "GS"

View File

@@ -4,14 +4,15 @@
# Aircraft identitification and category # Aircraft identitification and category
# ------------------------------------------ # ------------------------------------------
from pyModeS import common from __future__ import absolute_import, print_function, division
from pyModeS.decoder import common
def category(msg): def category(msg):
"""Aircraft category number """Aircraft category number
Args: Args:
msg (str): 28 hexdigits string msg (string): 28 bytes hexadecimal message string
Returns: Returns:
int: category number int: category number
@@ -29,7 +30,7 @@ def callsign(msg):
"""Aircraft callsign """Aircraft callsign
Args: Args:
msg (str): 28 hexdigits string msg (string): 28 bytes hexadecimal message string
Returns: Returns:
string: callsign string: callsign

View File

@@ -4,30 +4,32 @@
# Aircraft Airborn velocity # Aircraft Airborn velocity
# ------------------------------------------ # ------------------------------------------
from pyModeS import common from __future__ import absolute_import, print_function, division
from pyModeS.decoder import common
import math import math
def airborne_velocity(msg, source=False): def airborne_velocity(msg, rtn_sources=False):
"""Decode airborne velocity. """Calculate the speed, track (or heading), and vertical rate
Args: Args:
msg (str): 28 hexdigits string msg (string): 28 bytes hexadecimal message string
source (boolean): Include direction and vertical rate sources in return. Default to False. rtn_source (boolean): If the function will return
If set to True, the function will return six value instead of four. 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: Returns:
int, float, int, string, [string], [string]: Four or six parameters, including: (int, float, int, string, string, string): speed (kt),
- Speed (kt) ground track or heading (degree),
- Angle (degree), either ground track or heading rate of climb/descent (ft/min), speed type
- Vertical rate (ft/min) ('GS' for ground speed, 'AS' for airspeed),
- Speed type ('GS' for ground speed, 'AS' for airspeed) direction source ('true_north' for ground track / true north
- [Optional] Direction source ('TRUE_NORTH' or 'MAGENTIC_NORTH') as refrence, 'mag_north' for magnetic north as reference),
- [Optional] Vertical rate source ('BARO' or 'GNSS') rate of climb/descent source ('Baro' for barometer, 'GNSS'
for GNSS constellation).
""" """
if common.typecode(msg) != 19: if common.typecode(msg) != 19:
raise RuntimeError("%s: Not a airborne velocity message, expecting TC=19" % msg) raise RuntimeError("%s: Not a airborne velocity message, expecting TC=19" % msg)
@@ -59,9 +61,9 @@ def airborne_velocity(msg, source=False):
trk = math.degrees(trk) # convert to degrees trk = math.degrees(trk) # convert to degrees
trk = trk if trk >= 0 else trk + 360 # no negative val trk = trk if trk >= 0 else trk + 360 # no negative val
spd_type = "GS" tag = "GS"
trk_or_hdg = round(trk, 2) trk_or_hdg = round(trk, 2)
dir_type = "TRUE_NORTH" dir_type = "true_north"
else: else:
if mb[13] == "0": if mb[13] == "0":
@@ -78,33 +80,32 @@ def airborne_velocity(msg, source=False):
spd *= 4 spd *= 4
if mb[24] == "0": if mb[24] == "0":
spd_type = "IAS" tag = "IAS"
else: else:
spd_type = "TAS" tag = "TAS"
dir_type = "MAGENTIC_NORTH" dir_type = "mag_north"
vr_source = "GNSS" if mb[35] == "0" else "BARO" vr_source = "GNSS" if mb[35] == "0" else "Baro"
vr_sign = -1 if mb[36] == "1" else 1 vr_sign = -1 if mb[36] == "1" else 1
vr = common.bin2int(mb[37:46]) vr = common.bin2int(mb[37:46])
vs = None if vr == 0 else int(vr_sign * (vr - 1) * 64) rocd = None if vr == 0 else int(vr_sign * (vr - 1) * 64)
if source: if rtn_sources:
return spd, trk_or_hdg, vs, spd_type, dir_type, vr_source return spd, trk_or_hdg, rocd, tag, dir_type, vr_source
else: else:
return spd, trk_or_hdg, vs, spd_type return spd, trk_or_hdg, rocd, tag
def altitude_diff(msg): def altitude_diff(msg):
"""Decode the differece between GNSS and barometric altitude. """Decode the differece between GNSS and barometric altitude
Args: Args:
msg (str): 28 hexdigits string, TC=19 msg (string): 28 bytes hexadecimal message string, TC=19
Returns: Returns:
int: Altitude difference in feet. Negative value indicates GNSS altitude int: Altitude difference in ft. Negative value indicates GNSS altitude
below barometric altitude. below barometric altitude.
""" """
tc = common.typecode(msg) tc = common.typecode(msg)

View File

@@ -3,36 +3,37 @@
# Data link capability report # Data link capability report
# ------------------------------------------ # ------------------------------------------
from pyModeS import common from __future__ import absolute_import, print_function, division
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros
def is10(msg): def is10(msg):
"""Check if a message is likely to be BDS code 1,0 """Check if a message is likely to be BDS code 1,0
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message string
Returns: Returns:
bool: True or False bool: True or False
""" """
if common.allzeros(msg): if allzeros(msg):
return False return False
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
# first 8 bits must be 0x10 # first 8 bits must be 0x10
if d[0:8] != "00010000": if d[0:8] != "00010000":
return False return False
# bit 10 to 14 are reserved # bit 10 to 14 are reserved
if common.bin2int(d[9:14]) != 0: if bin2int(d[9:14]) != 0:
return False return False
# overlay capability conflict # overlay capabilty conflict
if d[14] == "1" and common.bin2int(d[16:23]) < 5: if d[14] == "1" and bin2int(d[16:23]) < 5:
return False return False
if d[14] == "0" and common.bin2int(d[16:23]) > 4: if d[14] == "0" and bin2int(d[16:23]) > 4:
return False return False
return True return True
@@ -42,11 +43,11 @@ def ovc10(msg):
"""Return the overlay control capability """Return the overlay control capability
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message string
Returns: Returns:
int: Whether the transponder is OVC capable int: Whether the transponder is OVC capable
""" """
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
return int(d[14]) return int(d[14])

View File

@@ -3,25 +3,27 @@
# Common usage GICB capability report # Common usage GICB capability report
# ------------------------------------------ # ------------------------------------------
from pyModeS import common
from __future__ import absolute_import, print_function, division
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros
def is17(msg): def is17(msg):
"""Check if a message is likely to be BDS code 1,7 """Check if a message is likely to be BDS code 1,7
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message string
Returns: Returns:
bool: True or False bool: True or False
""" """
if common.allzeros(msg): if allzeros(msg):
return False return False
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
if common.bin2int(d[24:56]) != 0: if bin2int(d[28:56]) != 0:
return False return False
caps = cap17(msg) caps = cap17(msg)
@@ -42,10 +44,10 @@ def cap17(msg):
"""Extract capacities from BDS 1,7 message """Extract capacities from BDS 1,7 message
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message string
Returns: Returns:
list: list of support BDS codes list: list of suport BDS codes
""" """
allbds = [ allbds = [
"05", "05",
@@ -72,10 +74,14 @@ def cap17(msg):
"56", "56",
"5F", "5F",
"60", "60",
"NA",
"NA",
"E1",
"E2",
] ]
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
idx = [i for i, v in enumerate(d[:24]) if v == "1"] idx = [i for i, v in enumerate(d[:28]) if v == "1"]
capacity = ["BDS" + allbds[i] for i in idx] capacity = ["BDS" + allbds[i] for i in idx if allbds[i] is not "NA"]
return capacity return capacity

View File

@@ -3,23 +3,24 @@
# Aircraft identification # Aircraft identification
# ------------------------------------------ # ------------------------------------------
from pyModeS import common from __future__ import absolute_import, print_function, division
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros
def is20(msg): def is20(msg):
"""Check if a message is likely to be BDS code 2,0 """Check if a message is likely to be BDS code 2,0
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message string
Returns: Returns:
bool: True or False bool: True or False
""" """
if common.allzeros(msg): if allzeros(msg):
return False return False
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
if d[0:8] != "00100000": if d[0:8] != "00100000":
return False return False
@@ -36,23 +37,23 @@ def cs20(msg):
"""Aircraft callsign """Aircraft callsign
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message (BDS40) string
Returns: Returns:
string: callsign, max. 8 chars string: callsign, max. 8 chars
""" """
chars = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ#####_###############0123456789######" chars = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ#####_###############0123456789######"
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
cs = "" cs = ""
cs += chars[common.bin2int(d[8:14])] cs += chars[bin2int(d[8:14])]
cs += chars[common.bin2int(d[14:20])] cs += chars[bin2int(d[14:20])]
cs += chars[common.bin2int(d[20:26])] cs += chars[bin2int(d[20:26])]
cs += chars[common.bin2int(d[26:32])] cs += chars[bin2int(d[26:32])]
cs += chars[common.bin2int(d[32:38])] cs += chars[bin2int(d[32:38])]
cs += chars[common.bin2int(d[38:44])] cs += chars[bin2int(d[38:44])]
cs += chars[common.bin2int(d[44:50])] cs += chars[bin2int(d[44:50])]
cs += chars[common.bin2int(d[50:56])] cs += chars[bin2int(d[50:56])]
return cs return cs

View File

@@ -3,23 +3,24 @@
# ACAS active resolution advisory # ACAS active resolution advisory
# ------------------------------------------ # ------------------------------------------
from pyModeS import common from __future__ import absolute_import, print_function, division
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros
def is30(msg): def is30(msg):
"""Check if a message is likely to be BDS code 2,0 """Check if a message is likely to be BDS code 2,0
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message string
Returns: Returns:
bool: True or False bool: True or False
""" """
if common.allzeros(msg): if allzeros(msg):
return False return False
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
if d[0:8] != "00110000": if d[0:8] != "00110000":
return False return False
@@ -29,7 +30,7 @@ def is30(msg):
return False return False
# reserved for ACAS III, in far future # reserved for ACAS III, in far future
if common.bin2int(d[15:22]) >= 48: if bin2int(d[15:22]) >= 48:
return False return False
return True return True

View File

@@ -3,48 +3,49 @@
# Selected vertical intention # Selected vertical intention
# ------------------------------------------ # ------------------------------------------
from __future__ import absolute_import, print_function, division
import warnings import warnings
from pyModeS import common from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros, wrongstatus
def is40(msg): def is40(msg):
"""Check if a message is likely to be BDS code 4,0 """Check if a message is likely to be BDS code 4,0
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message string
Returns: Returns:
bool: True or False bool: True or False
""" """
if common.allzeros(msg): if allzeros(msg):
return False return False
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
# status bit 1, 14, and 27 # status bit 1, 14, and 27
if common.wrongstatus(d, 1, 2, 13): if wrongstatus(d, 1, 2, 13):
return False return False
if common.wrongstatus(d, 14, 15, 26): if wrongstatus(d, 14, 15, 26):
return False return False
if common.wrongstatus(d, 27, 28, 39): if wrongstatus(d, 27, 28, 39):
return False return False
if common.wrongstatus(d, 48, 49, 51): if wrongstatus(d, 48, 49, 51):
return False return False
if common.wrongstatus(d, 54, 55, 56): if wrongstatus(d, 54, 55, 56):
return False return False
# bits 40-47 and 52-53 shall all be zero # bits 40-47 and 52-53 shall all be zero
if common.bin2int(d[39:47]) != 0: if bin2int(d[39:47]) != 0:
return False return False
if common.bin2int(d[51:53]) != 0: if bin2int(d[51:53]) != 0:
return False return False
return True return True
@@ -54,17 +55,17 @@ def selalt40mcp(msg):
"""Selected altitude, MCP/FCU """Selected altitude, MCP/FCU
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message (BDS40) string
Returns: Returns:
int: altitude in feet int: altitude in feet
""" """
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
if d[0] == "0": if d[0] == "0":
return None return None
alt = common.bin2int(d[1:13]) * 16 # ft alt = bin2int(d[1:13]) * 16 # ft
return alt return alt
@@ -72,17 +73,17 @@ def selalt40fms(msg):
"""Selected altitude, FMS """Selected altitude, FMS
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message (BDS40) string
Returns: Returns:
int: altitude in feet int: altitude in feet
""" """
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
if d[13] == "0": if d[13] == "0":
return None return None
alt = common.bin2int(d[14:26]) * 16 # ft alt = bin2int(d[14:26]) * 16 # ft
return alt return alt
@@ -90,17 +91,17 @@ def p40baro(msg):
"""Barometric pressure setting """Barometric pressure setting
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message (BDS40) string
Returns: Returns:
float: pressure in millibar float: pressure in millibar
""" """
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
if d[26] == "0": if d[26] == "0":
return None return None
p = common.bin2int(d[27:39]) * 0.1 + 800 # millibar p = bin2int(d[27:39]) * 0.1 + 800 # millibar
return p return p
@@ -117,4 +118,4 @@ def alt40fms(msg):
"alt40fms() has been renamed to selalt40fms(). It will be removed in the future.", "alt40fms() has been renamed to selalt40fms(). It will be removed in the future.",
DeprecationWarning, DeprecationWarning,
) )
return selalt40fms(msg) return selalt40mcp(msg)

View File

@@ -3,7 +3,8 @@
# Meteorological routine air report # Meteorological routine air report
# ------------------------------------------ # ------------------------------------------
from pyModeS import common from __future__ import absolute_import, print_function, division
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros, wrongstatus
def is44(msg): def is44(msg):
@@ -12,32 +13,32 @@ def is44(msg):
Meteorological routine air report Meteorological routine air report
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message string
Returns: Returns:
bool: True or False bool: True or False
""" """
if common.allzeros(msg): if allzeros(msg):
return False return False
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
# status bit 5, 35, 47, 50 # status bit 5, 35, 47, 50
if common.wrongstatus(d, 5, 6, 23): if wrongstatus(d, 5, 6, 23):
return False return False
if common.wrongstatus(d, 35, 36, 46): if wrongstatus(d, 35, 36, 46):
return False return False
if common.wrongstatus(d, 47, 48, 49): if wrongstatus(d, 47, 48, 49):
return False return False
if common.wrongstatus(d, 50, 51, 56): if wrongstatus(d, 50, 51, 56):
return False return False
# Bits 1-4 indicate source, values > 4 reserved and should not occur # Bits 1-4 indicate source, values > 4 reserved and should not occur
if common.bin2int(d[0:4]) > 4: if bin2int(d[0:4]) > 4:
return False return False
vw, dw = wind44(msg) vw, dw = wind44(msg)
@@ -55,20 +56,20 @@ def wind44(msg):
"""Wind speed and direction. """Wind speed and direction.
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message string
Returns: Returns:
(int, float): speed (kt), direction (degree) (int, float): speed (kt), direction (degree)
""" """
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
status = int(d[4]) status = int(d[4])
if not status: if not status:
return None, None return None, None
speed = common.bin2int(d[5:14]) # knots speed = bin2int(d[5:14]) # knots
direction = common.bin2int(d[14:23]) * 180.0 / 256.0 # degree direction = bin2int(d[14:23]) * 180.0 / 256.0 # degree
return round(speed, 0), round(direction, 1) return round(speed, 0), round(direction, 1)
@@ -77,18 +78,18 @@ def temp44(msg):
"""Static air temperature. """Static air temperature.
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message string
Returns: Returns:
float, float: temperature and alternative temperature in Celsius degree. float, float: temperature and alternative temperature in Celsius degree.
Note: Two values returns due to what seems to be an inconsistency Note: Two values returns due to what seems to be an inconsistancy
error in ICAO 9871 (2008) Appendix A-67. error in ICAO 9871 (2008) Appendix A-67.
""" """
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
sign = int(d[23]) sign = int(d[23])
value = common.bin2int(d[24:34]) value = bin2int(d[24:34])
if sign: if sign:
value = value - 1024 value = value - 1024
@@ -106,18 +107,18 @@ def p44(msg):
"""Static pressure. """Static pressure.
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message string
Returns: Returns:
int: static pressure in hPa int: static pressure in hPa
""" """
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
if d[34] == "0": if d[34] == "0":
return None return None
p = common.bin2int(d[35:46]) # hPa p = bin2int(d[35:46]) # hPa
return p return p
@@ -126,17 +127,17 @@ def hum44(msg):
"""humidity """humidity
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message string
Returns: Returns:
float: percentage of humidity, [0 - 100] % float: percentage of humidity, [0 - 100] %
""" """
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
if d[49] == "0": if d[49] == "0":
return None return None
hm = common.bin2int(d[50:56]) * 100.0 / 64 # % hm = bin2int(d[50:56]) * 100.0 / 64 # %
return round(hm, 1) return round(hm, 1)
@@ -145,17 +146,17 @@ def turb44(msg):
"""Turblence. """Turblence.
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message string
Returns: Returns:
int: turbulence level. 0=NIL, 1=Light, 2=Moderate, 3=Severe int: turbulence level. 0=NIL, 1=Light, 2=Moderate, 3=Severe
""" """
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
if d[46] == "0": if d[46] == "0":
return None return None
turb = common.bin2int(d[47:49]) turb = bin2int(d[47:49])
return turb return turb

View File

@@ -3,7 +3,8 @@
# Meteorological hazard report # Meteorological hazard report
# ------------------------------------------ # ------------------------------------------
from pyModeS import common from __future__ import absolute_import, print_function, division
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros, wrongstatus
def is45(msg): def is45(msg):
@@ -12,44 +13,44 @@ def is45(msg):
Meteorological hazard report Meteorological hazard report
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message string
Returns: Returns:
bool: True or False bool: True or False
""" """
if common.allzeros(msg): if allzeros(msg):
return False return False
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
# status bit 1, 4, 7, 10, 13, 16, 27, 39 # status bit 1, 4, 7, 10, 13, 16, 27, 39
if common.wrongstatus(d, 1, 2, 3): if wrongstatus(d, 1, 2, 3):
return False return False
if common.wrongstatus(d, 4, 5, 6): if wrongstatus(d, 4, 5, 6):
return False return False
if common.wrongstatus(d, 7, 8, 9): if wrongstatus(d, 7, 8, 9):
return False return False
if common.wrongstatus(d, 10, 11, 12): if wrongstatus(d, 10, 11, 12):
return False return False
if common.wrongstatus(d, 13, 14, 15): if wrongstatus(d, 13, 14, 15):
return False return False
if common.wrongstatus(d, 16, 17, 26): if wrongstatus(d, 16, 17, 26):
return False return False
if common.wrongstatus(d, 27, 28, 38): if wrongstatus(d, 27, 28, 38):
return False return False
if common.wrongstatus(d, 39, 40, 51): if wrongstatus(d, 39, 40, 51):
return False return False
# reserved # reserved
if common.bin2int(d[51:56]) != 0: if bin2int(d[51:56]) != 0:
return False return False
temp = temp45(msg) temp = temp45(msg)
@@ -64,17 +65,17 @@ def turb45(msg):
"""Turbulence. """Turbulence.
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message string
Returns: Returns:
int: Turbulence level. 0=NIL, 1=Light, 2=Moderate, 3=Severe int: Turbulence level. 0=NIL, 1=Light, 2=Moderate, 3=Severe
""" """
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
if d[0] == "0": if d[0] == "0":
return None return None
turb = common.bin2int(d[1:3]) turb = bin2int(d[1:3])
return turb return turb
@@ -82,17 +83,17 @@ def ws45(msg):
"""Wind shear. """Wind shear.
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message string
Returns: Returns:
int: Wind shear level. 0=NIL, 1=Light, 2=Moderate, 3=Severe int: Wind shear level. 0=NIL, 1=Light, 2=Moderate, 3=Severe
""" """
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
if d[3] == "0": if d[3] == "0":
return None return None
ws = common.bin2int(d[4:6]) ws = bin2int(d[4:6])
return ws return ws
@@ -100,17 +101,17 @@ def mb45(msg):
"""Microburst. """Microburst.
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message string
Returns: Returns:
int: Microburst level. 0=NIL, 1=Light, 2=Moderate, 3=Severe int: Microburst level. 0=NIL, 1=Light, 2=Moderate, 3=Severe
""" """
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
if d[6] == "0": if d[6] == "0":
return None return None
mb = common.bin2int(d[7:9]) mb = bin2int(d[7:9])
return mb return mb
@@ -118,17 +119,17 @@ def ic45(msg):
"""Icing. """Icing.
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message string
Returns: Returns:
int: Icing level. 0=NIL, 1=Light, 2=Moderate, 3=Severe int: Icing level. 0=NIL, 1=Light, 2=Moderate, 3=Severe
""" """
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
if d[9] == "0": if d[9] == "0":
return None return None
ic = common.bin2int(d[10:12]) ic = bin2int(d[10:12])
return ic return ic
@@ -136,17 +137,17 @@ def wv45(msg):
"""Wake vortex. """Wake vortex.
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message string
Returns: Returns:
int: Wake vortex level. 0=NIL, 1=Light, 2=Moderate, 3=Severe int: Wake vortex level. 0=NIL, 1=Light, 2=Moderate, 3=Severe
""" """
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
if d[12] == "0": if d[12] == "0":
return None return None
ws = common.bin2int(d[13:15]) ws = bin2int(d[13:15])
return ws return ws
@@ -154,16 +155,16 @@ def temp45(msg):
"""Static air temperature. """Static air temperature.
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message string
Returns: Returns:
float: tmeperature in Celsius degree float: tmeperature in Celsius degree
""" """
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
sign = int(d[16]) sign = int(d[16])
value = common.bin2int(d[17:26]) value = bin2int(d[17:26])
if sign: if sign:
value = value - 512 value = value - 512
@@ -178,16 +179,16 @@ def p45(msg):
"""Average static pressure. """Average static pressure.
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message string
Returns: Returns:
int: static pressure in hPa int: static pressure in hPa
""" """
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
if d[26] == "0": if d[26] == "0":
return None return None
p = common.bin2int(d[27:38]) # hPa p = bin2int(d[27:38]) # hPa
return p return p
@@ -195,14 +196,14 @@ def rh45(msg):
"""Radio height. """Radio height.
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message string
Returns: Returns:
int: radio height in ft int: radio height in ft
""" """
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
if d[38] == "0": if d[38] == "0":
return None return None
rh = common.bin2int(d[39:51]) * 16 rh = bin2int(d[39:51]) * 16
return rh return rh

View File

@@ -3,7 +3,8 @@
# Track and turn report # Track and turn report
# ------------------------------------------ # ------------------------------------------
from pyModeS import common from __future__ import absolute_import, print_function, division
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros, wrongstatus
def is50(msg): def is50(msg):
@@ -11,36 +12,36 @@ def is50(msg):
(Track and turn report) (Track and turn report)
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message string
Returns: Returns:
bool: True or False bool: True or False
""" """
if common.allzeros(msg): if allzeros(msg):
return False return False
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
# status bit 1, 12, 24, 35, 46 # status bit 1, 12, 24, 35, 46
if common.wrongstatus(d, 1, 3, 11): if wrongstatus(d, 1, 3, 11):
return False return False
if common.wrongstatus(d, 12, 13, 23): if wrongstatus(d, 12, 13, 23):
return False return False
if common.wrongstatus(d, 24, 25, 34): if wrongstatus(d, 24, 25, 34):
return False return False
if common.wrongstatus(d, 35, 36, 45): if wrongstatus(d, 35, 36, 45):
return False return False
if common.wrongstatus(d, 46, 47, 56): if wrongstatus(d, 46, 47, 56):
return False return False
roll = roll50(msg) roll = roll50(msg)
if (roll is not None) and abs(roll) > 50: if (roll is not None) and abs(roll) > 60:
return False return False
gs = gs50(msg) gs = gs50(msg)
@@ -61,19 +62,19 @@ def roll50(msg):
"""Roll angle, BDS 5,0 message """Roll angle, BDS 5,0 message
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message (BDS50) string
Returns: Returns:
float: angle in degrees, float: angle in degrees,
negative->left wing down, positive->right wing down negative->left wing down, positive->right wing down
""" """
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
if d[0] == "0": if d[0] == "0":
return None return None
sign = int(d[1]) # 1 -> left wing down sign = int(d[1]) # 1 -> left wing down
value = common.bin2int(d[2:11]) value = bin2int(d[2:11])
if sign: if sign:
value = value - 512 value = value - 512
@@ -86,18 +87,18 @@ def trk50(msg):
"""True track angle, BDS 5,0 message """True track angle, BDS 5,0 message
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message (BDS50) string
Returns: Returns:
float: angle in degrees to true north (from 0 to 360) float: angle in degrees to true north (from 0 to 360)
""" """
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
if d[11] == "0": if d[11] == "0":
return None return None
sign = int(d[12]) # 1 -> west sign = int(d[12]) # 1 -> west
value = common.bin2int(d[13:23]) value = bin2int(d[13:23])
if sign: if sign:
value = value - 1024 value = value - 1024
@@ -115,17 +116,17 @@ def gs50(msg):
"""Ground speed, BDS 5,0 message """Ground speed, BDS 5,0 message
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message (BDS50) string
Returns: Returns:
int: ground speed in knots int: ground speed in knots
""" """
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
if d[23] == "0": if d[23] == "0":
return None return None
spd = common.bin2int(d[24:34]) * 2 # kts spd = bin2int(d[24:34]) * 2 # kts
return spd return spd
@@ -133,12 +134,12 @@ def rtrk50(msg):
"""Track angle rate, BDS 5,0 message """Track angle rate, BDS 5,0 message
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message (BDS50) string
Returns: Returns:
float: angle rate in degrees/second float: angle rate in degrees/second
""" """
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
if d[34] == "0": if d[34] == "0":
return None return None
@@ -147,7 +148,7 @@ def rtrk50(msg):
return None return None
sign = int(d[35]) # 1 -> negative value, two's complement sign = int(d[35]) # 1 -> negative value, two's complement
value = common.bin2int(d[36:45]) value = bin2int(d[36:45])
if sign: if sign:
value = value - 512 value = value - 512
@@ -159,15 +160,15 @@ def tas50(msg):
"""Aircraft true airspeed, BDS 5,0 message """Aircraft true airspeed, BDS 5,0 message
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message (BDS50) string
Returns: Returns:
int: true airspeed in knots int: true airspeed in knots
""" """
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
if d[45] == "0": if d[45] == "0":
return None return None
tas = common.bin2int(d[46:56]) * 2 # kts tas = bin2int(d[46:56]) * 2 # kts
return tas return tas

View File

@@ -3,7 +3,8 @@
# Air-referenced state vector # Air-referenced state vector
# ------------------------------------------ # ------------------------------------------
from pyModeS import common from __future__ import absolute_import, print_function, division
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros, wrongstatus
def is53(msg): def is53(msg):
@@ -11,32 +12,32 @@ def is53(msg):
(Air-referenced state vector) (Air-referenced state vector)
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message string
Returns: Returns:
bool: True or False bool: True or False
""" """
if common.allzeros(msg): if allzeros(msg):
return False return False
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
# status bit 1, 13, 24, 34, 47 # status bit 1, 13, 24, 34, 47
if common.wrongstatus(d, 1, 3, 12): if wrongstatus(d, 1, 3, 12):
return False return False
if common.wrongstatus(d, 13, 14, 23): if wrongstatus(d, 13, 14, 23):
return False return False
if common.wrongstatus(d, 24, 25, 33): if wrongstatus(d, 24, 25, 33):
return False return False
if common.wrongstatus(d, 34, 35, 46): if wrongstatus(d, 34, 35, 46):
return False return False
if common.wrongstatus(d, 47, 49, 56): if wrongstatus(d, 47, 49, 56):
return False return False
ias = ias53(msg) ias = ias53(msg)
@@ -62,18 +63,18 @@ def hdg53(msg):
"""Magnetic heading, BDS 5,3 message """Magnetic heading, BDS 5,3 message
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message (BDS53) string
Returns: Returns:
float: angle in degrees to true north (from 0 to 360) float: angle in degrees to true north (from 0 to 360)
""" """
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
if d[0] == "0": if d[0] == "0":
return None return None
sign = int(d[1]) # 1 -> west sign = int(d[1]) # 1 -> west
value = common.bin2int(d[2:12]) value = bin2int(d[2:12])
if sign: if sign:
value = value - 1024 value = value - 1024
@@ -91,17 +92,17 @@ def ias53(msg):
"""Indicated airspeed, DBS 5,3 message """Indicated airspeed, DBS 5,3 message
Args: Args:
msg (str): 28 hexdigits msg (String): 28 bytes hexadecimal message
Returns: Returns:
int: indicated arispeed in knots int: indicated arispeed in knots
""" """
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
if d[12] == "0": if d[12] == "0":
return None return None
ias = common.bin2int(d[13:23]) # knots ias = bin2int(d[13:23]) # knots
return ias return ias
@@ -109,17 +110,17 @@ def mach53(msg):
"""MACH number, DBS 5,3 message """MACH number, DBS 5,3 message
Args: Args:
msg (str): 28 hexdigits msg (String): 28 bytes hexadecimal message
Returns: Returns:
float: MACH number float: MACH number
""" """
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
if d[23] == "0": if d[23] == "0":
return None return None
mach = common.bin2int(d[24:33]) * 0.008 mach = bin2int(d[24:33]) * 0.008
return round(mach, 3) return round(mach, 3)
@@ -127,17 +128,17 @@ def tas53(msg):
"""Aircraft true airspeed, BDS 5,3 message """Aircraft true airspeed, BDS 5,3 message
Args: Args:
msg (str): 28 hexdigits msg (String): 28 bytes hexadecimal message
Returns: Returns:
float: true airspeed in knots float: true airspeed in knots
""" """
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
if d[33] == "0": if d[33] == "0":
return None return None
tas = common.bin2int(d[34:46]) * 0.5 # kts tas = bin2int(d[34:46]) * 0.5 # kts
return round(tas, 1) return round(tas, 1)
@@ -145,18 +146,18 @@ def vr53(msg):
"""Vertical rate """Vertical rate
Args: Args:
msg (str): 28 hexdigits (BDS60) string msg (String): 28 bytes hexadecimal message (BDS60) string
Returns: Returns:
int: vertical rate in feet/minutes int: vertical rate in feet/minutes
""" """
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
if d[46] == "0": if d[46] == "0":
return None return None
sign = int(d[47]) # 1 -> negative value, two's complement sign = int(d[47]) # 1 -> negative value, two's complement
value = common.bin2int(d[48:56]) value = bin2int(d[48:56])
if value == 0 or value == 255: # all zeros or all ones if value == 0 or value == 255: # all zeros or all ones
return 0 return 0

View File

@@ -3,40 +3,40 @@
# Heading and speed report # Heading and speed report
# ------------------------------------------ # ------------------------------------------
from pyModeS import common from __future__ import absolute_import, print_function, division
from pyModeS.extra import aero from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros, wrongstatus
def is60(msg): def is60(msg):
"""Check if a message is likely to be BDS code 6,0 """Check if a message is likely to be BDS code 6,0
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message string
Returns: Returns:
bool: True or False bool: True or False
""" """
if common.allzeros(msg): if allzeros(msg):
return False return False
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
# status bit 1, 13, 24, 35, 46 # status bit 1, 13, 24, 35, 46
if common.wrongstatus(d, 1, 2, 12): if wrongstatus(d, 1, 2, 12):
return False return False
if common.wrongstatus(d, 13, 14, 23): if wrongstatus(d, 13, 14, 23):
return False return False
if common.wrongstatus(d, 24, 25, 34): if wrongstatus(d, 24, 25, 34):
return False return False
if common.wrongstatus(d, 35, 36, 45): if wrongstatus(d, 35, 36, 45):
return False return False
if common.wrongstatus(d, 46, 47, 56): if wrongstatus(d, 46, 47, 56):
return False return False
ias = ias60(msg) ias = ias60(msg)
@@ -55,14 +55,6 @@ def is60(msg):
if vr_ins is not None and abs(vr_ins) > 6000: if vr_ins is not None and abs(vr_ins) > 6000:
return False return False
# additional check knowing altitude
if (mach is not None) and (ias is not None) and (common.df(msg) == 20):
alt = common.altcode(msg)
if alt is not None:
ias_ = aero.mach2cas(mach, alt * aero.ft) / aero.kts
if abs(ias - ias_) > 20:
return False
return True return True
@@ -70,18 +62,18 @@ def hdg60(msg):
"""Megnetic heading of aircraft """Megnetic heading of aircraft
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message (BDS60) string
Returns: Returns:
float: heading in degrees to megnetic north (from 0 to 360) float: heading in degrees to megnetic north (from 0 to 360)
""" """
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
if d[0] == "0": if d[0] == "0":
return None return None
sign = int(d[1]) # 1 -> west sign = int(d[1]) # 1 -> west
value = common.bin2int(d[2:12]) value = bin2int(d[2:12])
if sign: if sign:
value = value - 1024 value = value - 1024
@@ -99,17 +91,17 @@ def ias60(msg):
"""Indicated airspeed """Indicated airspeed
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message (BDS60) string
Returns: Returns:
int: indicated airspeed in knots int: indicated airspeed in knots
""" """
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
if d[12] == "0": if d[12] == "0":
return None return None
ias = common.bin2int(d[13:23]) # kts ias = bin2int(d[13:23]) # kts
return ias return ias
@@ -117,17 +109,17 @@ def mach60(msg):
"""Aircraft MACH number """Aircraft MACH number
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message (BDS60) string
Returns: Returns:
float: MACH number float: MACH number
""" """
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
if d[23] == "0": if d[23] == "0":
return None return None
mach = common.bin2int(d[24:34]) * 2.048 / 512.0 mach = bin2int(d[24:34]) * 2.048 / 512.0
return round(mach, 3) return round(mach, 3)
@@ -135,18 +127,18 @@ def vr60baro(msg):
"""Vertical rate from barometric measurement, this value may be very noisy. """Vertical rate from barometric measurement, this value may be very noisy.
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message (BDS60) string
Returns: Returns:
int: vertical rate in feet/minutes int: vertical rate in feet/minutes
""" """
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
if d[34] == "0": if d[34] == "0":
return None return None
sign = int(d[35]) # 1 -> negative value, two's complement sign = int(d[35]) # 1 -> negative value, two's complement
value = common.bin2int(d[36:45]) value = bin2int(d[36:45])
if value == 0 or value == 511: # all zeros or all ones if value == 0 or value == 511: # all zeros or all ones
return 0 return 0
@@ -158,21 +150,21 @@ def vr60baro(msg):
def vr60ins(msg): def vr60ins(msg):
"""Vertical rate measurd by onbard equiments (IRS, AHRS) """Vertical rate messured by onbard equiments (IRS, AHRS)
Args: Args:
msg (str): 28 hexdigits string msg (String): 28 bytes hexadecimal message (BDS60) string
Returns: Returns:
int: vertical rate in feet/minutes int: vertical rate in feet/minutes
""" """
d = common.hex2bin(common.data(msg)) d = hex2bin(data(msg))
if d[45] == "0": if d[45] == "0":
return None return None
sign = int(d[46]) # 1 -> negative value, two's complement sign = int(d[46]) # 1 -> negative value, two's complement
value = common.bin2int(d[47:56]) value = bin2int(d[47:56])
if value == 0 or value == 511: # all zeros or all ones if value == 0 or value == 511: # all zeros or all ones
return 0 return 0

View File

@@ -1,83 +0,0 @@
# ------------------------------------------
# BDS 6,1
# ADS-B TC=28
# Aircraft Airborne status
# ------------------------------------------
from pyModeS import common
def is_emergency(msg: str) -> bool:
"""Check if the aircraft is reporting an emergency.
Non-emergencies are either a subtype of zero (no information) or
subtype of one and a value of zero (no emergency).
Subtype = 2 indicates an ACAS RA broadcast, look in BDS 3,0
:param msg: 28 bytes hexadecimal message string
:return: if the aircraft has declared an emergency
"""
if common.typecode(msg) != 28:
raise RuntimeError("%s: Not an airborne status message, expecting TC=28" % msg)
mb = common.hex2bin(msg)[32:]
subtype = common.bin2int(mb[5:8])
if subtype == 2:
raise RuntimeError("%s: Emergency message is ACAS-RA, not implemented")
emergency_state = common.bin2int(mb[8:11])
if subtype == 1 and emergency_state == 1:
return True
else:
return False
def emergency_state(msg: str) -> int:
"""Decode aircraft emergency state.
Value Meaning
----- -----------------------
0 No emergency
1 General emergency
2 Lifeguard/Medical
3 Minimum fuel
4 No communications
5 Unlawful communications
6-7 Reserved
:param msg: 28 bytes hexadecimal message string
:return: emergency state
"""
mb = common.hex2bin(msg)[32:]
subtype = common.bin2int(mb[5:8])
if subtype == 2:
raise RuntimeError("%s: Emergency message is ACAS-RA, not implemented")
emergency_state = common.bin2int(mb[8:11])
return emergency_state
def emergency_squawk(msg: str) -> str:
"""Decode squawk code.
Emergency value 1: squawk 7700.
Emergency value 4: squawk 7600.
Emergency value 5: squawk 7500.
:param msg: 28 bytes hexadecimal message string
:return: aircraft squawk code
"""
if common.typecode(msg) != 28:
raise RuntimeError("%s: Not an airborne status message, expecting TC=28" % msg)
msgbin = common.hex2bin(msg)
# construct the 13 bits Mode A ID code
idcode = msgbin[43:49] + "0" + msgbin[49:55]
squawk = common.squawk(idcode)
return squawk

View File

@@ -5,8 +5,7 @@ cdef unsigned char int_to_char(unsigned char i)
cpdef str hex2bin(str hexstr) cpdef str hex2bin(str hexstr)
cpdef long bin2int(str binstr) cpdef long bin2int(str binstr)
cpdef long hex2int(str hexstr) cpdef long hex2int(str binstr)
cpdef str bin2hex(str binstr)
cpdef unsigned char df(str msg) cpdef unsigned char df(str msg)
cpdef long crc(str msg, bint encode=*) cpdef long crc(str msg, bint encode=*)
@@ -17,12 +16,8 @@ cpdef bint is_icao_assigned(str icao)
cpdef int typecode(str msg) cpdef int typecode(str msg)
cpdef int cprNL(double lat) cpdef int cprNL(double lat)
cpdef str idcode(str msg) cpdef str idcode(str msg)
cpdef str squawk(str binstr)
cpdef int altcode(str msg) cpdef int altcode(str msg)
cpdef int altitude(str binstr)
cpdef str data(str msg) cdef str data(str msg)
cpdef bint allzeros(str msg) cpdef bint allzeros(str msg)

View File

@@ -5,7 +5,7 @@ from cpython cimport array
from cpython.bytes cimport PyBytes_GET_SIZE from cpython.bytes cimport PyBytes_GET_SIZE
from cpython.bytearray cimport PyByteArray_GET_SIZE from cpython.bytearray cimport PyByteArray_GET_SIZE
from libc.math cimport abs, cos, acos, fabs, M_PI as pi, floor as c_floor from libc.math cimport cos, acos, fabs, M_PI as pi, floor as c_floor
cdef int char_to_int(unsigned char binstr): cdef int char_to_int(unsigned char binstr):
@@ -66,11 +66,6 @@ cpdef long hex2int(str hexstr):
cumul = 16*cumul + char_to_int(v_hexstr[i]) cumul = 16*cumul + char_to_int(v_hexstr[i])
return cumul return cumul
@cython.boundscheck(False)
cpdef str bin2hex(str binstr):
return "{0:X}".format(int(binstr, 2))
@cython.boundscheck(False) @cython.boundscheck(False)
cpdef unsigned char df(str msg): cpdef unsigned char df(str msg):
"""Decode Downlink Format vaule, bits 1 to 5.""" """Decode Downlink Format vaule, bits 1 to 5."""
@@ -160,7 +155,17 @@ cpdef long floor(double x):
return <long> c_floor(x) return <long> c_floor(x)
cpdef str icao(str msg): cpdef str icao(str msg):
"""Calculate the ICAO address from an Mode-S message.""" """Calculate the ICAO address from an Mode-S message.
Applicable only with DF4, DF5, DF20, DF21 messages.
Args:
msg (String): 28 bytes hexadecimal message string
Returns:
String: ICAO address in 6 bytes hexadecimal string
"""
cdef unsigned char DF = df(msg) cdef unsigned char DF = df(msg)
cdef long c0, c1 cdef long c0, c1
@@ -207,7 +212,14 @@ cpdef bint is_icao_assigned(str icao):
@cython.boundscheck(False) @cython.boundscheck(False)
@cython.wraparound(False) @cython.wraparound(False)
cpdef int typecode(str msg): cpdef int typecode(str msg):
"""Type code of ADS-B message""" """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): if df(msg) not in (17, 18):
return -1 return -1
# return None # return None
@@ -219,11 +231,13 @@ cpdef int typecode(str msg):
cpdef int cprNL(double lat): cpdef int cprNL(double lat):
"""NL() function in CPR decoding.""" """NL() function in CPR decoding."""
if abs(lat) <= 1e-08: if lat == 0:
return 59 return 59
elif abs(abs(lat) - 87) <= 1e-08 + 1e-05 * 87:
if lat == 87 or lat == -87:
return 2 return 2
elif lat > 87 or lat < -87:
if lat > 87 or lat < -87:
return 1 return 1
cdef int nz = 15 cdef int nz = 15
@@ -236,41 +250,45 @@ cpdef int cprNL(double lat):
@cython.boundscheck(False) @cython.boundscheck(False)
@cython.wraparound(False) @cython.wraparound(False)
cpdef str idcode(str msg): cpdef str idcode(str msg):
"""Compute identity (squawk code).""" """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]: if df(msg) not in [5, 21]:
raise RuntimeError("Message must be Downlink Format 5 or 21.") raise RuntimeError("Message must be Downlink Format 5 or 21.")
squawk_code = squawk(hex2bin(msg)[19:32]) cdef bytearray _mbin = bytearray(hex2bin(msg).encode())
return squawk_code
@cython.boundscheck(False)
@cython.wraparound(False)
cpdef str squawk(str binstr):
"""Compute identity (squawk code)."""
if len(binstr) != 13 or set(binstr) != set('01'):
raise RuntimeError("Input must be 13 bits binary string")
cdef bytearray _mbin = bytearray(binstr.encode())
cdef unsigned char[:] mbin = _mbin cdef unsigned char[:] mbin = _mbin
cdef bytearray _idcode = bytearray(4) cdef bytearray _idcode = bytearray(4)
cdef unsigned char[:] idcode = _idcode cdef unsigned char[:] idcode = _idcode
cdef unsigned char C1 = mbin[0] cdef unsigned char C1 = mbin[19]
cdef unsigned char A1 = mbin[1] cdef unsigned char A1 = mbin[20]
cdef unsigned char C2 = mbin[2] cdef unsigned char C2 = mbin[21]
cdef unsigned char A2 = mbin[3] cdef unsigned char A2 = mbin[22]
cdef unsigned char C4 = mbin[4] cdef unsigned char C4 = mbin[23]
cdef unsigned char A4 = mbin[5] cdef unsigned char A4 = mbin[24]
# X = mbin[6] # _ = mbin[25]
cdef unsigned char B1 = mbin[7] cdef unsigned char B1 = mbin[26]
cdef unsigned char D1 = mbin[8] cdef unsigned char D1 = mbin[27]
cdef unsigned char B2 = mbin[9] cdef unsigned char B2 = mbin[28]
cdef unsigned char D2 = mbin[10] cdef unsigned char D2 = mbin[29]
cdef unsigned char B4 = mbin[11] cdef unsigned char B4 = mbin[30]
cdef unsigned char D4 = mbin[12] cdef unsigned char D4 = mbin[31]
# byte1 = int(A4 + A2 + A1, 2)
# byte2 = int(B4 + B2 + B1, 2)
# byte3 = int(C4 + C2 + C1, 2)
# byte4 = int(D4 + D2 + D1, 2)
idcode[0] = int_to_char((char_to_int(A4)*2 + char_to_int(A2))*2 + char_to_int(A1)) idcode[0] = int_to_char((char_to_int(A4)*2 + char_to_int(A2))*2 + char_to_int(A1))
idcode[1] = int_to_char((char_to_int(B4)*2 + char_to_int(B2))*2 + char_to_int(B1)) idcode[1] = int_to_char((char_to_int(B4)*2 + char_to_int(B2))*2 + char_to_int(B1))
@@ -279,68 +297,68 @@ cpdef str squawk(str binstr):
return _idcode.decode() return _idcode.decode()
#return str(byte1) + str(byte2) + str(byte3) + str(byte4)
@cython.boundscheck(False) @cython.boundscheck(False)
@cython.wraparound(False) @cython.wraparound(False)
cpdef int altcode(str msg): cpdef int altcode(str msg):
"""Compute the altitude.""" """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]: if df(msg) not in [0, 4, 16, 20]:
raise RuntimeError("Message must be Downlink Format 0, 4, 16, or 20.") raise RuntimeError("Message must be Downlink Format 0, 4, 16, or 20.")
alt = altitude(hex2bin(msg)[19:32]) # Altitude code, bit 20-32
return alt cdef bytearray _mbin = bytearray(hex2bin(msg).encode())
@cython.boundscheck(False)
@cython.wraparound(False)
cpdef int altitude(str binstr):
if len(binstr) != 13 or not set(binstr).issubset(set("01")):
raise RuntimeError("Input must be 13 bits binary string")
cdef bytearray _mbin = bytearray(binstr.encode())
cdef unsigned char[:] mbin = _mbin cdef unsigned char[:] mbin = _mbin
cdef char Mbit = binstr[6] cdef char mbit = mbin[25] # M bit: 26
cdef char Qbit = binstr[8] cdef char qbit = mbin[27] # Q bit: 28
cdef int alt = 0 cdef int alt = 0
cdef bytearray vbin cdef bytearray vbin
cdef bytearray _graybytes = bytearray(11) cdef bytearray _graybytes = bytearray(11)
cdef unsigned char[:] graybytes = _graybytes cdef unsigned char[:] graybytes = _graybytes
if bin2int(binstr) == 0: if mbit == 48: # unit in ft, "0" -> 48
# altitude unknown or invalid if qbit == 49: # 25ft interval, "1" -> 49
alt = -9999 vbin = _mbin[19:25] + _mbin[26:27] + _mbin[28:32]
elif Mbit == 48: # unit in ft, "0" -> 48
if Qbit == 49: # 25ft interval, "1" -> 49
vbin = _mbin[:6] + _mbin[7:8] + _mbin[9:]
alt = bin2int(vbin.decode()) * 25 - 1000 alt = bin2int(vbin.decode()) * 25 - 1000
if Qbit == 48: # 100ft interval, above 50175ft, "0" -> 48 if qbit == 48: # 100ft interval, above 50175ft, "0" -> 48
graybytes[8] = mbin[0] graybytes[8] = mbin[19]
graybytes[2] = mbin[1] graybytes[2] = mbin[20]
graybytes[9] = mbin[2] graybytes[9] = mbin[21]
graybytes[3] = mbin[3] graybytes[3] = mbin[22]
graybytes[10] = mbin[4] graybytes[10] = mbin[23]
graybytes[4] = mbin[5] graybytes[4] = mbin[24]
# M = mbin[6] # _ = mbin[25]
graybytes[5] = mbin[7] graybytes[5] = mbin[26]
# Q = mbin[8] # cdef char D1 = mbin[27] # always zero
graybytes[6] = mbin[9] graybytes[6] = mbin[28]
graybytes[0] = mbin[10] graybytes[0] = mbin[29]
graybytes[7] = mbin[11] graybytes[7] = mbin[30]
graybytes[1] = mbin[12] graybytes[1] = mbin[31]
# graybytes = D2 + D4 + A1 + A2 + A4 + B1 + B2 + B4 + C1 + C2 + C4
alt = gray2alt(_graybytes.decode()) alt = gray2alt(_graybytes.decode())
elif Mbit == 49: # unit in meter, "1" -> 49 if mbit == 49: # unit in meter, "1" -> 49
vbin = _mbin[:6] + _mbin[7:] vbin = _mbin[19:25] + _mbin[26:31]
alt = int(bin2int(vbin.decode()) * 3.28084) # convert to ft alt = int(bin2int(vbin.decode()) * 3.28084) # convert to ft
return alt return alt
cpdef int gray2alt(str codestr): cpdef int gray2alt(str codestr):
cdef str gc500 = codestr[:8] cdef str gc500 = codestr[:8]
cdef int n500 = gray2int(gc500) cdef int n500 = gray2int(gc500)
@@ -373,13 +391,21 @@ cdef int gray2int(str graystr):
return num return num
cpdef str data(str msg): cdef str data(str msg):
"""Return the data frame in the message, bytes 9 to 22.""" """Return the data frame in the message, bytes 9 to 22."""
return msg[8:-6] return msg[8:-6]
cpdef bint allzeros(str msg): cpdef bint allzeros(str msg):
"""Check if the data bits are all zeros.""" """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)) d = hex2bin(data(msg))
if bin2int(d) > 0: if bin2int(d) > 0:

View File

@@ -1,27 +1,26 @@
"""Comm-B module. """Comm-B Wrapper.
The Comm-B module imports all functions from the following modules: The Comm-B wrapper imports all functions from the following modules:
ELS - elementary surveillance **ELS - elementary surveillance**
- pyModeS.decoder.bds.bds10
- pyModeS.decoder.bds.bds17
- pyModeS.decoder.bds.bds20
- pyModeS.decoder.bds.bds30
- pyModeS.decoder.bds.bds10 **EHS - enhanced surveillance**
- pyModeS.decoder.bds.bds17 - pyModeS.decoder.bds.bds40
- pyModeS.decoder.bds.bds20 - pyModeS.decoder.bds.bds50
- pyModeS.decoder.bds.bds30 - pyModeS.decoder.bds.bds60
EHS - enhanced surveillance **MRAR and MHR**
- pyModeS.decoder.bds.bds44
- pyModeS.decoder.bds.bds40 - pyModeS.decoder.bds.bds45
- 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 # ELS - elementary surveillance
from pyModeS.decoder.bds.bds10 import * from pyModeS.decoder.bds.bds10 import *
from pyModeS.decoder.bds.bds17 import * from pyModeS.decoder.bds.bds17 import *

View File

@@ -1,46 +1,40 @@
from typing import Optional from __future__ import absolute_import, print_function, division
import numpy as np import numpy as np
from textwrap import wrap from textwrap import wrap
def hex2bin(hexstr: str) -> str: def hex2bin(hexstr):
"""Convert a hexdecimal string to binary string, with zero fillings.""" """Convert a hexdecimal string to binary string, with zero fillings."""
num_of_bits = len(hexstr) * 4 num_of_bits = len(hexstr) * 4
binstr = bin(int(hexstr, 16))[2:].zfill(int(num_of_bits)) binstr = bin(int(hexstr, 16))[2:].zfill(int(num_of_bits))
return binstr return binstr
def hex2int(hexstr: str) -> int: def hex2int(hexstr):
"""Convert a hexdecimal string to integer.""" """Convert a hexdecimal string to integer."""
return int(hexstr, 16) return int(hexstr, 16)
def bin2int(binstr: str) -> int: def bin2int(binstr):
"""Convert a binary string to integer.""" """Convert a binary string to integer."""
return int(binstr, 2) return int(binstr, 2)
def bin2hex(binstr: str) -> str: def df(msg):
"""Convert a binary string to hexdecimal string.""" """Decode Downlink Format vaule, bits 1 to 5."""
return "{0:X}".format(int(binstr, 2))
def df(msg: str) -> int:
"""Decode Downlink Format value, bits 1 to 5."""
dfbin = hex2bin(msg[:2]) dfbin = hex2bin(msg[:2])
return min(bin2int(dfbin[0:5]), 24) return min(bin2int(dfbin[0:5]), 24)
def crc(msg: str, encode: bool = False) -> int: def crc(msg, encode=False):
"""Mode-S Cyclic Redundancy Check. """Mode-S Cyclic Redundancy Check.
Detect if bit error occurs in the Mode-S message. When encode option is on, Detect if bit error occurs in the Mode-S message. When encode option is on,
the checksum is generated. the checksum is generated.
Args: Args:
msg: 28 bytes hexadecimal message string msg (string): 28 bytes hexadecimal message string
encode: True to encode the date only and return the checksum encode (bool): True to encode the date only and return the checksum
Returns: Returns:
int: message checksum, or partity bits (encoder) int: message checksum, or partity bits (encoder)
@@ -77,7 +71,7 @@ def crc(msg: str, encode: bool = False) -> int:
return result return result
def crc_legacy(msg: str, encode: bool = False) -> int: def crc_legacy(msg, encode=False):
"""Mode-S Cyclic Redundancy Check. (Legacy code, 2x slow).""" """Mode-S Cyclic Redundancy Check. (Legacy code, 2x slow)."""
# the polynominal generattor code for CRC [1111111111111010000001001] # the polynominal generattor code for CRC [1111111111111010000001001]
generator = np.array( generator = np.array(
@@ -105,7 +99,7 @@ def crc_legacy(msg: str, encode: bool = False) -> int:
return reminder return reminder
def floor(x: float) -> int: def floor(x):
"""Mode-S floor function. """Mode-S floor function.
Defined as the greatest integer value k, such that k <= x Defined as the greatest integer value k, such that k <= x
@@ -115,7 +109,7 @@ def floor(x: float) -> int:
return int(np.floor(x)) return int(np.floor(x))
def icao(msg: str) -> Optional[str]: def icao(msg):
"""Calculate the ICAO address from an Mode-S message. """Calculate the ICAO address from an Mode-S message.
Applicable only with DF4, DF5, DF20, DF21 messages. Applicable only with DF4, DF5, DF20, DF21 messages.
@@ -127,7 +121,6 @@ def icao(msg: str) -> Optional[str]:
String: ICAO address in 6 bytes hexadecimal string String: ICAO address in 6 bytes hexadecimal string
""" """
addr: Optional[str]
DF = df(msg) DF = df(msg)
if DF in (11, 17, 18): if DF in (11, 17, 18):
@@ -142,7 +135,7 @@ def icao(msg: str) -> Optional[str]:
return addr return addr
def is_icao_assigned(icao: str) -> bool: def is_icao_assigned(icao):
"""Check whether the ICAO address is assigned (Annex 10, Vol 3).""" """Check whether the ICAO address is assigned (Annex 10, Vol 3)."""
if (icao is None) or (not isinstance(icao, str)) or (len(icao) != 6): if (icao is None) or (not isinstance(icao, str)) or (len(icao) != 6):
return False return False
@@ -171,7 +164,7 @@ def is_icao_assigned(icao: str) -> bool:
return True return True
def typecode(msg: str) -> Optional[int]: def typecode(msg):
"""Type code of ADS-B message """Type code of ADS-B message
Args: Args:
@@ -187,14 +180,16 @@ def typecode(msg: str) -> Optional[int]:
return bin2int(tcbin[0:5]) return bin2int(tcbin[0:5])
def cprNL(lat: float) -> int: def cprNL(lat):
"""NL() function in CPR decoding.""" """NL() function in CPR decoding."""
if np.isclose(lat, 0): if lat == 0:
return 59 return 59
elif np.isclose(abs(lat), 87):
if lat == 87 or lat == -87:
return 2 return 2
elif lat > 87 or lat < -87:
if lat > 87 or lat < -87:
return 1 return 1
nz = 15 nz = 15
@@ -205,8 +200,11 @@ def cprNL(lat: float) -> int:
return NL return NL
def idcode(msg: str) -> str: def idcode(msg):
"""Compute identity code (squawk) encoded in DF5 or DF21 message. """Compute identity (squawk code).
Applicable only for DF5 or DF21 messages, bit 20-32.
credit: @fbyrkjeland
Args: Args:
msg (String): 28 bytes hexadecimal message string msg (String): 28 bytes hexadecimal message string
@@ -219,37 +217,20 @@ def idcode(msg: str) -> str:
raise RuntimeError("Message must be Downlink Format 5 or 21.") raise RuntimeError("Message must be Downlink Format 5 or 21.")
mbin = hex2bin(msg) mbin = hex2bin(msg)
idcodebin = mbin[19:32]
return squawk(idcodebin) C1 = mbin[19]
A1 = mbin[20]
C2 = mbin[21]
def squawk(binstr: str) -> str: A2 = mbin[22]
"""Decode 13 bits identity (squawk) code. C4 = mbin[23]
A4 = mbin[24]
Args: # _ = mbin[25]
binstr (String): 13 bits binary string B1 = mbin[26]
D1 = mbin[27]
Returns: B2 = mbin[28]
int: altitude in ft D2 = mbin[29]
B4 = mbin[30]
""" D4 = mbin[31]
if len(binstr) != 13 or not set(binstr).issubset(set("01")):
raise RuntimeError("Input must be 13 bits binary string")
C1 = binstr[0]
A1 = binstr[1]
C2 = binstr[2]
A2 = binstr[3]
C4 = binstr[4]
A4 = binstr[5]
# X = binstr[6]
B1 = binstr[7]
D1 = binstr[8]
B2 = binstr[9]
D2 = binstr[10]
B4 = binstr[11]
D4 = binstr[12]
byte1 = int(A4 + A2 + A1, 2) byte1 = int(A4 + A2 + A1, 2)
byte2 = int(B4 + B2 + B1, 2) byte2 = int(B4 + B2 + B1, 2)
@@ -259,8 +240,11 @@ def squawk(binstr: str) -> str:
return str(byte1) + str(byte2) + str(byte3) + str(byte4) return str(byte1) + str(byte2) + str(byte3) + str(byte4)
def altcode(msg: str) -> Optional[int]: def altcode(msg):
"""Compute altitude encoded in DF4 or DF20 message. """Compute the altitude.
Applicable only for DF4 or DF20 message, bit 20-32.
credit: @fbyrkjeland
Args: Args:
msg (String): 28 bytes hexadecimal message string msg (String): 28 bytes hexadecimal message string
@@ -269,78 +253,50 @@ def altcode(msg: str) -> Optional[int]:
int: altitude in ft int: altitude in ft
""" """
alt: Optional[int]
if df(msg) not in [0, 4, 16, 20]: if df(msg) not in [0, 4, 16, 20]:
raise RuntimeError("Message must be Downlink Format 0, 4, 16, or 20.") raise RuntimeError("Message must be Downlink Format 0, 4, 16, or 20.")
# Altitude code, bit 20-32 # Altitude code, bit 20-32
mbin = hex2bin(msg) mbin = hex2bin(msg)
altitude_code = mbin[19:32] mbit = mbin[25] # M bit: 26
qbit = mbin[27] # Q bit: 28
alt = altitude(altitude_code) if mbit == "0": # unit in ft
if qbit == "1": # 25ft interval
return alt vbin = mbin[19:25] + mbin[26] + mbin[28:32]
def altitude(binstr: str) -> Optional[int]:
"""Decode 13 bits altitude code.
Args:
binstr (String): 13 bits binary string
Returns:
int: altitude in ft
"""
alt: Optional[int]
if len(binstr) != 13 or not set(binstr).issubset(set("01")):
raise RuntimeError("Input must be 13 bits binary string")
Mbit = binstr[6]
Qbit = binstr[8]
if bin2int(binstr) == 0:
# altitude unknown or invalid
alt = None
elif Mbit == "0": # unit in ft
if Qbit == "1": # 25ft interval
vbin = binstr[:6] + binstr[7] + binstr[9:]
alt = bin2int(vbin) * 25 - 1000 alt = bin2int(vbin) * 25 - 1000
if Qbit == "0": # 100ft interval, above 50187.5ft if qbit == "0": # 100ft interval, above 50175ft
C1 = binstr[0] C1 = mbin[19]
A1 = binstr[1] A1 = mbin[20]
C2 = binstr[2] C2 = mbin[21]
A2 = binstr[3] A2 = mbin[22]
C4 = binstr[4] C4 = mbin[23]
A4 = binstr[5] A4 = mbin[24]
# M = binstr[6] # _ = mbin[25]
B1 = binstr[7] B1 = mbin[26]
# Q = binstr[8] # D1 = mbin[27] # always zero
B2 = binstr[9] B2 = mbin[28]
D2 = binstr[10] D2 = mbin[29]
B4 = binstr[11] B4 = mbin[30]
D4 = binstr[12] D4 = mbin[31]
graystr = D2 + D4 + A1 + A2 + A4 + B1 + B2 + B4 + C1 + C2 + C4 graystr = D2 + D4 + A1 + A2 + A4 + B1 + B2 + B4 + C1 + C2 + C4
alt = gray2alt(graystr) alt = gray2alt(graystr)
if Mbit == "1": # unit in meter if mbit == "1": # unit in meter
vbin = binstr[:6] + binstr[7:] vbin = mbin[19:25] + mbin[26:31]
alt = int(bin2int(vbin) * 3.28084) # convert to ft alt = int(bin2int(vbin) * 3.28084) # convert to ft
return alt return alt
def gray2alt(binstr: str) -> Optional[int]: def gray2alt(codestr):
gc500 = binstr[:8] gc500 = codestr[:8]
n500 = gray2int(gc500) n500 = gray2int(gc500)
# in 100-ft step must be converted first # in 100-ft step must be converted first
gc100 = binstr[8:] gc100 = codestr[8:]
n100 = gray2int(gc100) n100 = gray2int(gc100)
if n100 in [0, 5, 6]: if n100 in [0, 5, 6]:
@@ -356,9 +312,9 @@ def gray2alt(binstr: str) -> Optional[int]:
return alt return alt
def gray2int(binstr: str) -> int: def gray2int(graystr):
"""Convert greycode to binary.""" """Convert greycode to binary."""
num = bin2int(binstr) num = bin2int(graystr)
num ^= num >> 8 num ^= num >> 8
num ^= num >> 4 num ^= num >> 4
num ^= num >> 2 num ^= num >> 2
@@ -366,12 +322,12 @@ def gray2int(binstr: str) -> int:
return num return num
def data(msg: str) -> str: def data(msg):
"""Return the data frame in the message, bytes 9 to 22.""" """Return the data frame in the message, bytes 9 to 22."""
return msg[8:-6] return msg[8:-6]
def allzeros(msg: str) -> bool: def allzeros(msg):
"""Check if the data bits are all zeros. """Check if the data bits are all zeros.
Args: Args:
@@ -389,7 +345,7 @@ def allzeros(msg: str) -> bool:
return True return True
def wrongstatus(data: str, sb: int, msb: int, lsb: int) -> bool: def wrongstatus(data, sb, msb, lsb):
"""Check if the status bit and field bits are consistency. """Check if the status bit and field bits are consistency.
This Function is used for checking BDS code versions. This Function is used for checking BDS code versions.

View File

@@ -9,6 +9,7 @@ The EHS wrapper imports all functions from the following modules:
""" """
from __future__ import absolute_import, print_function, division
import warnings import warnings
from pyModeS.decoder.bds.bds40 import * from pyModeS.decoder.bds.bds40 import *

View File

@@ -10,6 +10,8 @@ The ELS wrapper imports all functions from the following modules:
""" """
from __future__ import absolute_import, print_function, division
from pyModeS.decoder.bds.bds10 import * from pyModeS.decoder.bds.bds10 import *
from pyModeS.decoder.bds.bds17 import * from pyModeS.decoder.bds.bds17 import *
from pyModeS.decoder.bds.bds20 import * from pyModeS.decoder.bds.bds20 import *

View File

@@ -1,132 +1,8 @@
""" """
Decode short roll call surveillance replies, with downlink format 4 or 5 Warpper for short roll call surveillance replies DF=4/5
[To be implemented]
""" """
from pyModeS import common from __future__ import absolute_import, print_function, division
from pyModeS.decoder import common
def _checkdf(func):
"""Ensure downlink format is 4 or 5."""
def wrapper(msg):
df = common.df(msg)
if df not in [4, 5]:
raise RuntimeError(
"Incorrect downlink format, expect 4 or 5, got {}".format(df)
)
return func(msg)
return wrapper
@_checkdf
def fs(msg):
"""Decode flight status.
Args:
msg (str): 14 hexdigits string
Returns:
int, str: flight status, description
"""
msgbin = common.hex2bin(msg)
fs = common.bin2int(msgbin[5:8])
text = None
if fs == 0:
text = "no alert, no SPI, aircraft is airborne"
elif fs == 1:
text = "no alert, no SPI, aircraft is on-ground"
elif fs == 2:
text = "alert, no SPI, aircraft is airborne"
elif fs == 3:
text = "alert, no SPI, aircraft is on-ground"
elif fs == 4:
text = "alert, SPI, aircraft is airborne or on-ground"
elif fs == 5:
text = "no alert, SPI, aircraft is airborne or on-ground"
return fs, text
@_checkdf
def dr(msg):
"""Decode downlink request.
Args:
msg (str): 14 hexdigits string
Returns:
int, str: downlink request, description
"""
msgbin = common.hex2bin(msg)
dr = common.bin2int(msgbin[8:13])
text = None
if dr == 0:
text = "no downlink request"
elif dr == 1:
text = "request to send Comm-B message"
elif dr == 4:
text = "Comm-B broadcast 1 available"
elif dr == 5:
text = "Comm-B broadcast 2 available"
elif dr >= 16:
text = "ELM downlink segments available: {}".format(dr - 15)
return dr, text
@_checkdf
def um(msg):
"""Decode utility message.
Utility message contains interrogator identifier and reservation type.
Args:
msg (str): 14 hexdigits string
Returns:
int, str: interrogator identifier code that triggered the reply, and
reservation type made by the interrogator
"""
msgbin = common.hex2bin(msg)
iis = common.bin2int(msgbin[13:17])
ids = common.bin2int(msgbin[17:19])
if ids == 0:
ids_text = None
if ids == 1:
ids_text = "Comm-B interrogator identifier code"
if ids == 2:
ids_text = "Comm-C interrogator identifier code"
if ids == 3:
ids_text = "Comm-D interrogator identifier code"
return iis, ids, ids_text
@_checkdf
def altitude(msg):
"""Decode altitude.
Args:
msg (String): 14 hexdigits string
Returns:
int: altitude in ft
"""
return common.altcode(msg)
@_checkdf
def identity(msg):
"""Decode squawk code.
Args:
msg (String): 14 hexdigits string
Returns:
string: squawk code
"""
return common.idcode(msg)

View File

@@ -1,221 +0,0 @@
from pyModeS import common
from textwrap import wrap
def uplink_icao(msg):
"""Calculate the ICAO address from a Mode-S interrogation (uplink message)"""
p_gen = 0xFFFA0480 << ((len(msg) - 14) * 4)
data = int(msg[:-6], 16)
PA = int(msg[-6:], 16)
ad = 0
topbit = 0b1 << (len(msg) * 4 - 25)
for j in range(0, len(msg) * 4, 1):
if data & topbit:
data ^= p_gen
data = (data << 1) + ((PA >> 23) & 1)
PA = PA << 1
if j > (len(msg) * 4 - 26):
ad = ad + ((data >> (len(msg) * 4 - 25)) & 1)
ad = ad << 1
return "%06X" % (ad >> 2)
def uf(msg):
"""Decode Uplink Format value, bits 1 to 5."""
ufbin = common.hex2bin(msg[:2])
return min(common.bin2int(ufbin[0:5]), 24)
def bds(msg):
"""Decode requested BDS register from selective (Roll Call) interrogation."""
UF = uf(msg)
msgbin = common.hex2bin(msg)
msgbin_split = wrap(msgbin, 8)
mbytes = list(map(common.bin2int, msgbin_split))
if uf(msg) in {4, 5, 20, 21}:
di = mbytes[1] & 0x7 # DI - Designator Identification
RR = mbytes[1] >> 3 & 0x1F
if RR > 15:
BDS1 = RR - 16
if di == 7:
RRS = mbytes[2] & 0x0F
BDS2 = RRS
elif di == 3:
RRS = ((mbytes[2] & 0x1) << 4) | ((mbytes[3] & 0xE0) >> 5)
BDS2 = RRS
else:
BDS2 = 0 # for other values of DI, the BDS2 is assumed 0 (as per ICAO Annex 10 Vol IV)
return str(BDS1) + str(BDS2)
else:
return None
else:
return None
def pr(msg):
"""Decode PR (probability of reply) field from All Call interrogation.
Interpretation:
0 signifies reply with probability of 1
1 signifies reply with probability of 1/2
2 signifies reply with probability of 1/4
3 signifies reply with probability of 1/8
4 signifies reply with probability of 1/16
5, 6, 7 not assigned
8 signifies disregard lockout, reply with probability of 1
9 signifies disregard lockout, reply with probability of 1/2
10 signifies disregard lockout, reply with probability of 1/4
11 signifies disregard lockout, reply with probability of 1/8
12 signifies disregard lockout, reply with probability of 1/16
13, 14, 15 not assigned.
"""
msgbin = common.hex2bin(msg)
msgbin_split = wrap(msgbin, 8)
mbytes = list(map(common.bin2int, msgbin_split))
if uf(msg) == 11:
return ((mbytes[0] & 0x7) << 1) | ((mbytes[1] & 0x80) >> 7)
else:
return None
def ic(msg):
"""Decode IC (interrogator code) from a ground-based interrogation."""
UF = uf(msg)
msgbin = common.hex2bin(msg)
msgbin_split = wrap(msgbin, 8)
mbytes = list(map(common.bin2int, msgbin_split))
IC = None
BDS2 = ""
if uf(msg) == 11:
codeLabel = mbytes[1] & 0x7
icField = (mbytes[1] >> 3) & 0xF
# Store the Interogator Code
ic_switcher = {
0: "II" + str(icField),
1: "SI" + str(icField),
2: "SI" + str(icField + 16),
3: "SI" + str(icField + 32),
4: "SI" + str(icField + 48),
}
IC = ic_switcher.get(codeLabel, "")
if uf(msg) in {4, 5, 20, 21}:
di = mbytes[1] & 0x7
RR = mbytes[1] >> 3 & 0x1F
if RR > 15:
BDS1 = RR - 16
if di == 0 or di == 1 or di == 7:
# II
II = (mbytes[2] >> 4) & 0xF
IC = "II" + str(II)
elif di == 3:
# SI
SI = (mbytes[2] >> 2) & 0x3F
IC = "SI" + str(SI)
return IC
def lockout(msg):
"""Decode the lockout command from selective (Roll Call) interrogation."""
msgbin = common.hex2bin(msg)
msgbin_split = wrap(msgbin, 8)
mbytes = list(map(common.bin2int, msgbin_split))
if uf(msg) in {4, 5, 20, 21}:
lockout = False
di = mbytes[1] & 0x7
if di == 7:
# LOS
if ((mbytes[3] & 0x40) >> 6) == 1:
lockout = True
elif di == 3:
# LSS
if ((mbytes[2] & 0x2) >> 1) == 1:
lockout = True
return lockout
else:
return None
def uplink_fields(msg):
"""Decode individual fields of a ground-based interrogation."""
msgbin = common.hex2bin(msg)
msgbin_split = wrap(msgbin, 8)
mbytes = list(map(common.bin2int, msgbin_split))
PR = ""
LOS = ""
IC = ""
lockout = False
di = ""
RR = ""
RRS = ""
BDS1 = ""
BDS2 = ""
if uf(msg) == 11:
# Probability of Reply decoding
PR = ((mbytes[0] & 0x7) << 1) | ((mbytes[1] & 0x80) >> 7)
# Get cl and ic bit fields from the data
# Decode the SI or II interrogator code
codeLabel = mbytes[1] & 0x7
icField = (mbytes[1] >> 3) & 0xF
# Store the Interogator Code
ic_switcher = {
0: "II" + str(icField),
1: "SI" + str(icField),
2: "SI" + str(icField + 16),
3: "SI" + str(icField + 32),
4: "SI" + str(icField + 48),
}
IC = ic_switcher.get(codeLabel, "")
if uf(msg) in {4, 5, 20, 21}:
# Decode the DI and get the lockout information conveniently (LSS or LOS)
# DI - Designator Identification
di = mbytes[1] & 0x7
RR = mbytes[1] >> 3 & 0x1F
if RR > 15:
BDS1 = RR - 16
BDS2 = 0
if di == 0 or di == 1:
# II
II = (mbytes[2] >> 4) & 0xF
IC = "II" + str(II)
elif di == 7:
# LOS
if ((mbytes[3] & 0x40) >> 6) == 1:
lockout = True
# II
II = (mbytes[2] >> 4) & 0xF
IC = "II" + str(II)
RRS = mbytes[2] & 0x0F
BDS2 = RRS
elif di == 3:
# LSS
if ((mbytes[2] & 0x2) >> 1) == 1:
lockout = True
# SI
SI = (mbytes[2] >> 2) & 0x3F
IC = "SI" + str(SI)
RRS = ((mbytes[2] & 0x1) << 4) | ((mbytes[3] & 0xE0) >> 5)
BDS2 = RRS
return {
"DI": di,
"IC": IC,
"LOS": lockout,
"PR": PR,
"RR": RR,
"RRS": RRS,
"BDS": str(BDS1) + str(BDS2),
}

View File

@@ -0,0 +1 @@
from __future__ import absolute_import, print_function, division

View File

@@ -9,9 +9,9 @@ International Standard Atmosphere
p,rho,T = atmos(H) # atmos as function of geopotential altitude H [m] p,rho,T = atmos(H) # atmos as function of geopotential altitude H [m]
a = vsound(H) # speed of sound [m/s] as function of H[m] a = vsound(H) # speed of sound [m/s] as function of H[m]
p = pressure(H) # calls atmos but returns only pressure [Pa] p = pressure(H) # calls atmos but retruns only pressure [Pa]
T = temperature(H) # calculates temperature [K] T = temperature(H) # calculates temperature [K]
rho = density(H) # calls atmos but returns only pressure [Pa] rho = density(H) # calls atmos but retruns only pressure [Pa]
Speed conversion at altitude H[m] in ISA Speed conversion at altitude H[m] in ISA
:: ::

View File

@@ -1,120 +1,90 @@
import time
import traceback
import numpy as np import numpy as np
import pyModeS as pms import pyModeS as pms
from rtlsdr import RtlSdr
import time
try: modes_sample_rate = 2e6
import rtlsdr
except:
print("------------------------------------------------------------------------")
print("! Warining: pyrtlsdr not installed (required for using RTL-SDR devices) ")
print("------------------------------------------------------------------------")
sampling_rate = 2e6
smaples_per_microsec = 2
modes_frequency = 1090e6 modes_frequency = 1090e6
buffer_size = 1024 * 200 buffer_size = 1024 * 100
read_size = 1024 * 100 read_size = 1024 * 20
pbits = 8 pbits = 8
fbits = 112 fbits = 112
preamble = [1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0] preamble = [1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0]
th_amp = 0.2 # signal amplitude threshold for 0 and 1 bit
th_amp_diff = 0.8 # signal amplitude threshold difference between 0 and 1 bit th_amp_diff = 0.8 # signal amplitude threshold difference between 0 and 1 bit
class RtlReader(object): class RtlReader(object):
def __init__(self, **kwargs): def __init__(self, **kwargs):
super(RtlReader, self).__init__() super(RtlReader, self).__init__()
self.signal_buffer = [] # amplitude of the sample only self.signal_buffer = []
self.sdr = rtlsdr.RtlSdr() self.sdr = RtlSdr()
self.sdr.sample_rate = sampling_rate self.sdr.sample_rate = modes_sample_rate
self.sdr.center_freq = modes_frequency self.sdr.center_freq = modes_frequency
self.sdr.gain = "auto" self.sdr.gain = "auto"
# sdr.freq_correction = 75
self.debug = kwargs.get("debug", False) self.debug = kwargs.get("debug", False)
self.raw_pipe_in = None self.raw_pipe_in = None
self.stop_flag = False self.stop_flag = False
self.noise_floor = 1e6
self.exception_queue = None
def _calc_noise(self):
"""Calculate noise floor"""
window = smaples_per_microsec * 100
total_len = len(self.signal_buffer)
means = (
np.array(self.signal_buffer[: total_len // window * window])
.reshape(-1, window)
.mean(axis=1)
)
return min(means)
def _process_buffer(self): def _process_buffer(self):
"""process raw IQ data in the buffer"""
# update noise floor
self.noise_floor = min(self._calc_noise(), self.noise_floor)
# set minimum signal amplitude
min_sig_amp = 3.162 * self.noise_floor # 10 dB SNR
# Mode S messages
messages = [] messages = []
# signal_array = np.array(self.signal_buffer)
# pulses_array = np.where(np.array(self.signal_buffer) < th_amp, 0, 1)
# pulses = "".join(str(x) for x in pulses_array)
buffer_length = len(self.signal_buffer) buffer_length = len(self.signal_buffer)
i = 0 i = 0
while i < buffer_length: while i < buffer_length:
if self.signal_buffer[i] < min_sig_amp: if self.signal_buffer[i] < th_amp:
i += 1 i += 1
continue continue
# if pulses[i : i + pbits * 2] == preamble:
if self._check_preamble(self.signal_buffer[i : i + pbits * 2]): if self._check_preamble(self.signal_buffer[i : i + pbits * 2]):
frame_start = i + pbits * 2 frame_start = i + pbits * 2
frame_end = i + pbits * 2 + (fbits + 1) * 2 frame_end = i + pbits * 2 + (fbits + 1) * 2
frame_length = (fbits + 1) * 2 frame_length = (fbits + 1) * 2
frame_pulses = self.signal_buffer[frame_start:frame_end] frame_pulses = self.signal_buffer[frame_start:frame_end]
threshold = max(frame_pulses) * 0.2 msgbin = ""
msgbin = []
for j in range(0, frame_length, 2): for j in range(0, frame_length, 2):
p2 = frame_pulses[j : j + 2] p2 = frame_pulses[j : j + 2]
if len(p2) < 2: if len(p2) < 2:
break break
if p2[0] < threshold and p2[1] < threshold: if p2[0] < th_amp and p2[1] < th_amp:
break break
elif p2[0] >= p2[1]: elif p2[0] >= p2[1]:
c = 1 c = "1"
elif p2[0] < p2[1]: elif p2[0] < p2[1]:
c = 0 c = "0"
else: else:
msgbin = [] msgbin = ""
break break
msgbin += c
msgbin.append(c)
# advance i with a jump # advance i with a jump
i = frame_start + j i = frame_start + j
if len(msgbin) > 0: if len(msgbin) > 0:
msghex = pms.bin2hex("".join([str(i) for i in msgbin])) msghex = pms.bin2hex(msgbin)
if self._check_msg(msghex): if self._check_msg(msghex):
messages.append([msghex, time.time()]) messages.append([msghex, time.time()])
if self.debug: if self.debug:
self._debug_msg(msghex) self._debug_msg(msghex)
# elif i > buffer_length - 500: elif i > buffer_length - 500:
# # save some for next process # save some for next process
# break break
else: else:
i += 1 i += 1
# reset the buffer # keep reminder of buffer for next iteration
self.signal_buffer = self.signal_buffer[i:] self.signal_buffer = self.signal_buffer[i:]
return messages return messages
def _check_preamble(self, pulses): def _check_preamble(self, pulses):
@@ -152,8 +122,10 @@ class RtlReader(object):
pass pass
def _read_callback(self, data, rtlsdr_obj): def _read_callback(self, data, rtlsdr_obj):
# scaling signal (imporatant)
amp = np.absolute(data) amp = np.absolute(data)
self.signal_buffer.extend(amp.tolist()) amp_norm = np.interp(amp, (amp.min(), amp.max()), (0, 1))
self.signal_buffer.extend(amp_norm.tolist())
if len(self.signal_buffer) >= buffer_size: if len(self.signal_buffer) >= buffer_size:
messages = self._process_buffer() messages = self._process_buffer()
@@ -166,25 +138,18 @@ class RtlReader(object):
pass pass
def stop(self, *args, **kwargs): def stop(self, *args, **kwargs):
self.sdr.close() self.sdr.cancel_read_async()
def run(self, raw_pipe_in=None, stop_flag=None, exception_queue=None): def run(self, raw_pipe_in=None, stop_flag=None):
self.raw_pipe_in = raw_pipe_in self.raw_pipe_in = raw_pipe_in
self.exception_queue = exception_queue
self.stop_flag = stop_flag self.stop_flag = stop_flag
self.sdr.read_samples_async(self._read_callback, read_size)
try: # count = 1
# raise RuntimeError("test exception") # while count < 1000:
# count += 1
while True: # data = self.sdr.read_samples(read_size)
data = self.sdr.read_samples(read_size) # self._read_callback(data, None)
self._read_callback(data, None)
except Exception as e:
tb = traceback.format_exc()
if self.exception_queue is not None:
self.exception_queue.put(tb)
raise e
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -1,5 +1,6 @@
"""Stream beast raw data from a TCP server, convert to mode-s messages.""" """Stream beast raw data from a TCP server, convert to mode-s messages."""
from __future__ import print_function, division
import os import os
import sys import sys
import time import time
@@ -7,6 +8,11 @@ import pyModeS as pms
import traceback import traceback
import zmq import zmq
if sys.version_info > (3, 0):
PY_VERSION = 3
else:
PY_VERSION = 2
class TcpClient(object): class TcpClient(object):
def __init__(self, host, port, datatype): def __init__(self, host, port, datatype):
@@ -23,8 +29,6 @@ class TcpClient(object):
self.raw_pipe_in = None self.raw_pipe_in = None
self.stop_flag = False self.stop_flag = False
self.exception_queue = None
def connect(self): def connect(self):
self.socket = zmq.Context().socket(zmq.STREAM) self.socket = zmq.Context().socket(zmq.STREAM)
self.socket.setsockopt(zmq.LINGER, 0) self.socket.setsockopt(zmq.LINGER, 0)
@@ -166,7 +170,7 @@ class TcpClient(object):
Start character '$' Start character '$'
MS field - Payload MS field - Payload
Position 1 through 14: Postion 1 through 14:
14 bytes = 112 bits 14 bytes = 112 bits
Mode-S payload Mode-S payload
In case of DF types that only carry 7 bytes of information In case of DF types that only carry 7 bytes of information
@@ -250,9 +254,8 @@ class TcpClient(object):
for msg, t in messages: for msg, t in messages:
print("%15.9f %s" % (t, msg)) print("%15.9f %s" % (t, msg))
def run(self, raw_pipe_in=None, stop_flag=None, exception_queue=None): def run(self, raw_pipe_in=None, stop_flag=None):
self.raw_pipe_in = raw_pipe_in self.raw_pipe_in = raw_pipe_in
self.exception_queue = exception_queue
self.stop_flag = stop_flag self.stop_flag = stop_flag
self.connect() self.connect()
@@ -260,9 +263,17 @@ class TcpClient(object):
try: try:
received = [i for i in self.socket.recv(4096)] received = [i for i in self.socket.recv(4096)]
if PY_VERSION == 2:
received = [ord(i) for i in received]
self.buffer.extend(received) self.buffer.extend(received)
# print(''.join(x.encode('hex') for x in self.buffer)) # print(''.join(x.encode('hex') for x in self.buffer))
# process self.buffer when it is longer enough
# if len(self.buffer) < 2048:
# continue
# -- Removed!! Cause delay in low data rate scenario --
if self.datatype == "beast": if self.datatype == "beast":
messages = self.read_beast_buffer() messages = self.read_beast_buffer()
elif self.datatype == "raw": elif self.datatype == "raw":
@@ -275,15 +286,22 @@ class TcpClient(object):
else: else:
self.handle_messages(messages) self.handle_messages(messages)
# raise RuntimeError("test exception")
except zmq.error.Again:
continue
except Exception as e: except Exception as e:
tb = traceback.format_exc() # Provides the user an option to supply the environment
if self.exception_queue is not None: # variable PYMODES_DEBUG to halt the execution
self.exception_queue.put(tb) # for debugging purposes
raise e 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()
time.sleep(1)
except Exception as e:
print("Unexpected Error:", e)
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -1,3 +1,4 @@
from __future__ import absolute_import, print_function, division
import os import os
import time import time
import datetime import datetime
@@ -26,7 +27,7 @@ class Decode:
self.dumpto = None self.dumpto = None
def process_raw(self, adsb_ts, adsb_msg, commb_ts, commb_msg, tnow=None): def process_raw(self, adsb_ts, adsb_msg, commb_ts, commb_msg, tnow=None):
"""process a chunk of adsb and commb messages received in the same """process a chunk of adsb and commb messages recieved in the same
time period. time period.
""" """
if tnow is None: if tnow is None:
@@ -258,31 +259,26 @@ class Decode:
return return
def get_aircraft(self): def get_aircraft(self):
"""all aircraft that are stored in memory""" """all aircraft that are stored in memeory"""
acs = self.acs acs = self.acs
return acs return acs
def run(self, raw_pipe_out, ac_pipe_in, exception_queue): def run(self, raw_pipe_out, ac_pipe_in):
local_buffer = [] local_buffer = []
while True: while True:
try: while raw_pipe_out.poll():
while raw_pipe_out.poll(): data = raw_pipe_out.recv()
data = raw_pipe_out.recv() local_buffer.append(data)
local_buffer.append(data)
for data in local_buffer: for data in local_buffer:
self.process_raw( self.process_raw(
data["adsb_ts"], data["adsb_ts"],
data["adsb_msg"], data["adsb_msg"],
data["commb_ts"], data["commb_ts"],
data["commb_msg"], data["commb_msg"],
) )
local_buffer = [] local_buffer = []
acs = self.get_aircraft() acs = self.get_aircraft()
ac_pipe_in.send(acs) ac_pipe_in.send(acs)
time.sleep(0.001) time.sleep(0.001)
except Exception as e:
tb = traceback.format_exc()
exception_queue.put((e, tb))

View File

@@ -1,8 +1,8 @@
#!/usr/bin/env python #!/usr/bin/env python
from __future__ import print_function, division
import os import os
import sys import sys
import time
import argparse import argparse
import curses import curses
import signal import signal
@@ -12,6 +12,10 @@ from pyModeS.streamer.screen import Screen
from pyModeS.streamer.source import NetSource, RtlSdrSource from pyModeS.streamer.source import NetSource, RtlSdrSource
# redirect all stdout to null, avoiding messing up with the screen
sys.stdout = open(os.devnull, "w")
support_rawtypes = ["raw", "beast", "skysense"] support_rawtypes = ["raw", "beast", "skysense"]
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
@@ -23,9 +27,8 @@ parser.add_argument(
) )
parser.add_argument( parser.add_argument(
"--connect", "--connect",
help="Define server, port and data type. Supported data types are: {}".format( help="Define server, port and data type. Supported data types are: %s"
support_rawtypes % support_rawtypes,
),
nargs=3, nargs=3,
metavar=("SERVER", "PORT", "DATATYPE"), metavar=("SERVER", "PORT", "DATATYPE"),
default=None, default=None,
@@ -68,7 +71,7 @@ elif SOURCE == "net":
else: else:
SERVER, PORT, DATATYPE = args.connect SERVER, PORT, DATATYPE = args.connect
if DATATYPE not in support_rawtypes: if DATATYPE not in support_rawtypes:
print("Data type not supported, available ones are %s" % support_rawtypes) print("Data type not supported, avaiable ones are %s" % support_rawtypes)
else: else:
print('Source must be "rtlsdr" or "net".') print('Source must be "rtlsdr" or "net".')
@@ -84,13 +87,13 @@ if DUMPTO is not None:
sys.exit(1) sys.exit(1)
# redirect all stdout to null, avoiding messing up with the screen # raw_event = multiprocessing.Event()
sys.stdout = open(os.devnull, "w") # ac_event = multiprocessing.Event()
# raw_queue = multiprocessing.Queue()
# ac_queue = multiprocessing.Queue()
raw_pipe_in, raw_pipe_out = multiprocessing.Pipe() raw_pipe_in, raw_pipe_out = multiprocessing.Pipe()
ac_pipe_in, ac_pipe_out = multiprocessing.Pipe() ac_pipe_in, ac_pipe_out = multiprocessing.Pipe()
exception_queue = multiprocessing.Queue()
stop_flag = multiprocessing.Value("b", False) stop_flag = multiprocessing.Value("b", False)
if SOURCE == "net": if SOURCE == "net":
@@ -99,38 +102,29 @@ elif SOURCE == "rtlsdr":
source = RtlSdrSource() source = RtlSdrSource()
recv_process = multiprocessing.Process( recv_process = multiprocessing.Process(target=source.run, args=(raw_pipe_in, stop_flag))
target=source.run, args=(raw_pipe_in, stop_flag, exception_queue)
)
decode = Decode(latlon=LATLON, dumpto=DUMPTO) decode = Decode(latlon=LATLON, dumpto=DUMPTO)
decode_process = multiprocessing.Process( decode_process = multiprocessing.Process(
target=decode.run, args=(raw_pipe_out, ac_pipe_in, exception_queue) target=decode.run, args=(raw_pipe_out, ac_pipe_in)
) )
screen = Screen(uncertainty=UNCERTAINTY) screen = Screen(uncertainty=UNCERTAINTY)
screen_process = multiprocessing.Process( screen_process = multiprocessing.Process(target=screen.run, args=(ac_pipe_out,))
target=screen.run, args=(ac_pipe_out, exception_queue)
)
def shutdown(): def closeall(signal, frame):
print("KeyboardInterrupt (ID: {}). Cleaning up...".format(signal))
stop_flag.value = True stop_flag.value = True
curses.endwin() curses.endwin()
sys.stdout = sys.__stdout__
recv_process.terminate() recv_process.terminate()
decode_process.terminate() decode_process.terminate()
screen_process.terminate() screen_process.terminate()
recv_process.join() recv_process.join()
decode_process.join() decode_process.join()
screen_process.join() screen_process.join()
exit(0)
def closeall(signal, frame):
print("KeyboardInterrupt (ID: {}). Cleaning up...".format(signal))
shutdown()
sys.exit(0)
signal.signal(signal.SIGINT, closeall) signal.signal(signal.SIGINT, closeall)
@@ -138,19 +132,3 @@ signal.signal(signal.SIGINT, closeall)
recv_process.start() recv_process.start()
decode_process.start() decode_process.start()
screen_process.start() screen_process.start()
while True:
if (
(not recv_process.is_alive())
or (not decode_process.is_alive())
or (not screen_process.is_alive())
):
shutdown()
while not exception_queue.empty():
trackback = exception_queue.get()
print(trackback)
sys.exit(1)
time.sleep(0.01)

View File

@@ -1,8 +1,8 @@
from __future__ import print_function, division
import curses import curses
import numpy as np import numpy as np
import time import time
import threading import threading
import traceback
COLUMNS = [ COLUMNS = [
("call", 10), ("call", 10),
@@ -187,32 +187,24 @@ class Screen(object):
self.screen.refresh() self.screen.refresh()
self.draw_frame() self.draw_frame()
def run(self, ac_pipe_out, exception_queue): def run(self, ac_pipe_out):
local_buffer = [] local_buffer = []
key_thread = threading.Thread(target=self.kye_handling) key_thread = threading.Thread(target=self.kye_handling)
key_thread.daemon = True
key_thread.start() key_thread.start()
while True: while True:
while ac_pipe_out.poll():
acs = ac_pipe_out.recv()
local_buffer.append(acs)
for acs in local_buffer:
self.update_ac(acs)
local_buffer = []
try: try:
# raise RuntimeError("test exception")
while ac_pipe_out.poll():
acs = ac_pipe_out.recv()
local_buffer.append(acs)
for acs in local_buffer:
self.update_ac(acs)
local_buffer = []
self.update() self.update()
except curses.error: except:
pass pass
except Exception as e:
tb = traceback.format_exc()
exception_queue.put(tb)
time.sleep(0.1)
raise e
time.sleep(0.001) time.sleep(0.001)

View File

@@ -4,16 +4,24 @@ See:
https://packaging.python.org/en/latest/distributing.html https://packaging.python.org/en/latest/distributing.html
https://github.com/pypa/sampleproject https://github.com/pypa/sampleproject
Steps for deploying a new version: Steps for deploying a new verison:
1. Increase the version number 1. Increase the version number
2. remove the old deployment under [dist] and [build] folder 2. remove the old deployment under [dist] and [build] folder
3. run: python setup.py sdist 3. run: python setup.py sdist
run: python setup.py bdist_wheel --universal
4. twine upload dist/* 4. twine upload dist/*
""" """
# Always prefer setuptools over distutils # Always prefer setuptools over distutils
from setuptools import setup, find_packages from setuptools import setup, find_packages
# Compile some parts
from setuptools.extension import Extension
from Cython.Build import cythonize
extensions = [Extension("pyModeS.decoder.c_common", ["pyModeS/decoder/c_common.pyx"])]
# To use a consistent encoding # To use a consistent encoding
from codecs import open from codecs import open
from os import path from os import path
@@ -24,38 +32,78 @@ here = path.abspath(path.dirname(__file__))
with open(path.join(here, "README.rst"), encoding="utf-8") as f: with open(path.join(here, "README.rst"), encoding="utf-8") as f:
long_description = f.read() long_description = f.read()
setup(
details = dict(
name="pyModeS", name="pyModeS",
version="2.8", # 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="2.4",
description="Python Mode-S and ADS-B Decoder", description="Python Mode-S and ADS-B Decoder",
long_description=long_description, long_description=long_description,
# The project's main homepage.
url="https://github.com/junzis/pyModeS", url="https://github.com/junzis/pyModeS",
# Author details
author="Junzi Sun", author="Junzi Sun",
author_email="j.sun-1@tudelft.nl", author_email="j.sun-1@tudelft.nl",
# Choose your license
license="GNU GPL v3", license="GNU GPL v3",
# See https://pypi.python.org/pypi?%3Aaction=list_classifiers
classifiers=[ classifiers=[
# How mature is this project? Common values are
# 3 - Alpha
# 4 - Beta
# 5 - Production/Stable
"Development Status :: 4 - Beta", "Development Status :: 4 - Beta",
# Indicate who your project is intended for
"Intended Audience :: Developers", "Intended Audience :: Developers",
"Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries",
# Pick your license as you wish (should match "license" above)
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)", "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
# Specify the Python versions you support here. In particular, ensure
# that you indicate whether you support Python 2, Python 3 or both.
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
], ],
ext_modules=cythonize(extensions),
# What does your project relate to?
keywords="Mode-S ADS-B EHS ELS Comm-B", keywords="Mode-S ADS-B EHS ELS Comm-B",
# 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"]),
install_requires=["numpy", "pyzmq"], # Alternatively, if you want to distribute just a my_module.py, uncomment
extras_require={"fast": ["Cython"]}, # this:
package_data={"pyModeS": ["*.pyx", "*.pxd"]}, # 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=["numpy", "argparse", "pyzmq", "pyrtlsdr"],
# List additional groups of dependencies here (e.g. development
# dependencies). You can install these using the following syntax,
# for example:
# $ pip install -e .[dev,test]
# extras_require={
# '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.
# entry_points={
# 'console_scripts': [
# 'sample=sample:main',
# ],
# },
scripts=["pyModeS/streamer/modeslive"], scripts=["pyModeS/streamer/modeslive"],
) )
try:
from setuptools.extension import Extension
from Cython.Build import cythonize
extensions = [Extension("pyModeS.c_common", ["pyModeS/c_common.pyx"])]
setup(**dict(details, ext_modules=cythonize(extensions)))
except:
setup(**details)

View File

@@ -1,3 +1,4 @@
from __future__ import print_function
from pyModeS import commb, common, bds from pyModeS import commb, common, bds
# === Decode sample data file === # === Decode sample data file ===
@@ -46,7 +47,7 @@ def bds_info(BDS, m):
) )
else: else:
info = [] info = None
return info return info
@@ -87,5 +88,5 @@ def commb_decode_all(df, n=None):
if __name__ == "__main__": if __name__ == "__main__":
commb_decode_all(df=20, n=500) commb_decode_all(df=20, n=100)
commb_decode_all(df=21, n=500) commb_decode_all(df=21, n=100)

View File

@@ -76,16 +76,10 @@ def test_adsb_velocity():
vgs_surface = adsb.velocity("8FC8200A3AB8F5F893096B000000") vgs_surface = adsb.velocity("8FC8200A3AB8F5F893096B000000")
assert vgs == (159, 182.88, -832, "GS") assert vgs == (159, 182.88, -832, "GS")
assert vas == (375, 243.98, -2304, "TAS") assert vas == (375, 243.98, -2304, "TAS")
assert vgs_surface == (19, 42.2, 0, "GS") assert vgs_surface == (19.0, 42.2, 0, "GS")
assert adsb.altitude_diff("8D485020994409940838175B284F") == 550 assert adsb.altitude_diff("8D485020994409940838175B284F") == 550
def test_adsb_emergency():
assert not adsb.is_emergency("8DA2C1B6E112B600000000760759")
assert adsb.emergency_state("8DA2C1B6E112B600000000760759") == 0
assert adsb.emergency_squawk("8DA2C1B6E112B600000000760759") == "6615"
# def test_nic(): # def test_nic():
# assert adsb.nic('8D3C70A390AB11F55B8C57F65FE6') == 0 # assert adsb.nic('8D3C70A390AB11F55B8C57F65FE6') == 0
# assert adsb.nic('8DE1C9738A4A430B427D219C8225') == 1 # assert adsb.nic('8DE1C9738A4A430B427D219C8225') == 1

View File

@@ -1,13 +0,0 @@
from pyModeS import allcall
def test_icao():
assert allcall.icao("5D484FDEA248F5") == "484FDE"
def test_interrogator():
assert allcall.interrogator("5D484FDEA248F5") == 22
def test_capability():
assert allcall.capability("5D484FDEA248F5")[0] == 5

View File

@@ -17,8 +17,8 @@ def test_bds_infer():
def test_bds_is50or60(): def test_bds_is50or60():
assert bds.is50or60("A0001838201584F23468207CDFA5", 0, 0, 0) == None assert bds.is50or60("A0001838201584F23468207CDFA5", 0, 0, 0) == None
assert bds.is50or60("A8001EBCFFFB23286004A73F6A5B", 320, 250, 14000) == "BDS50" assert bds.is50or60("A0000000FFDA9517000464000000", 182, 237, 1250) == "BDS50"
assert bds.is50or60("A8001EBCFE1B29287FDCA807BCFC", 320, 250, 14000) == "BDS50" assert bds.is50or60("A0000000919A5927E23444000000", 413, 54, 18700) == "BDS60"
def test_surface_position(): def test_surface_position():

View File

@@ -1,60 +1,60 @@
try: from pyModeS.decoder import c_common as common
from pyModeS import c_common
def test_conversions():
assert c_common.hex2bin("6E") == "01101110"
assert c_common.bin2hex("01101110") == "6E"
assert c_common.bin2hex("1101110") == "6E"
def test_crc_decode():
assert c_common.crc("8D406B902015A678D4D220AA4BDA") == 0
assert c_common.crc("8d8960ed58bf053cf11bc5932b7d") == 0
assert c_common.crc("8d45cab390c39509496ca9a32912") == 0
assert c_common.crc("8d74802958c904e6ef4ba0184d5c") == 0
assert c_common.crc("8d4400cd9b0000b4f87000e71a10") == 0
assert c_common.crc("8d4065de58a1054a7ef0218e226a") == 0
assert c_common.crc("c80b2dca34aa21dd821a04cb64d4") == 10719924
assert c_common.crc("a800089d8094e33a6004e4b8a522") == 4805588
assert c_common.crc("a8000614a50b6d32bed000bbe0ed") == 5659991
assert c_common.crc("a0000410bc900010a40000f5f477") == 11727682
assert c_common.crc("8d4ca251204994b1c36e60a5343d") == 16
assert c_common.crc("b0001718c65632b0a82040715b65") == 353333
def test_crc_encode():
parity = c_common.crc("8D406B902015A678D4D220AA4BDA", encode=True)
assert parity == 11160538
def test_icao():
assert c_common.icao("8D406B902015A678D4D220AA4BDA") == "406B90"
assert c_common.icao("A0001839CA3800315800007448D9") == "400940"
assert c_common.icao("A000139381951536E024D4CCF6B5") == "3C4DD2"
assert c_common.icao("A000029CFFBAA11E2004727281F1") == "4243D0"
def test_modes_altcode():
assert c_common.altcode("A02014B400000000000000F9D514") == 32300
def test_modes_idcode():
assert c_common.idcode("A800292DFFBBA9383FFCEB903D01") == "1346"
def test_graycode_to_altitude():
assert c_common.gray2alt("00000000010") == -1000
assert c_common.gray2alt("00000001010") == -500
assert c_common.gray2alt("00000011011") == -100
assert c_common.gray2alt("00000011010") == 0
assert c_common.gray2alt("00000011110") == 100
assert c_common.gray2alt("00000010011") == 600
assert c_common.gray2alt("00000110010") == 1000
assert c_common.gray2alt("00001001001") == 5800
assert c_common.gray2alt("00011100100") == 10300
assert c_common.gray2alt("01100011010") == 32000
assert c_common.gray2alt("01110000100") == 46300
assert c_common.gray2alt("01010101100") == 50200
assert c_common.gray2alt("11011110100") == 73200
assert c_common.gray2alt("10000000011") == 126600
assert c_common.gray2alt("10000000001") == 126700
except: def test_conversions():
pass assert common.hex2bin("6E406B") == "011011100100000001101011"
def test_crc_decode():
assert common.crc("8D406B902015A678D4D220AA4BDA") == 0
assert common.crc("8d8960ed58bf053cf11bc5932b7d") == 0
assert common.crc("8d45cab390c39509496ca9a32912") == 0
assert common.crc("8d74802958c904e6ef4ba0184d5c") == 0
assert common.crc("8d4400cd9b0000b4f87000e71a10") == 0
assert common.crc("8d4065de58a1054a7ef0218e226a") == 0
assert common.crc("c80b2dca34aa21dd821a04cb64d4") == 10719924
assert common.crc("a800089d8094e33a6004e4b8a522") == 4805588
assert common.crc("a8000614a50b6d32bed000bbe0ed") == 5659991
assert common.crc("a0000410bc900010a40000f5f477") == 11727682
assert common.crc("8d4ca251204994b1c36e60a5343d") == 16
assert common.crc("b0001718c65632b0a82040715b65") == 353333
def test_crc_encode():
parity = common.crc("8D406B902015A678D4D220AA4BDA", encode=True)
assert parity == 11160538
def test_icao():
assert common.icao("8D406B902015A678D4D220AA4BDA") == "406B90"
assert common.icao("A0001839CA3800315800007448D9") == "400940"
assert common.icao("A000139381951536E024D4CCF6B5") == "3C4DD2"
assert common.icao("A000029CFFBAA11E2004727281F1") == "4243D0"
def test_modes_altcode():
assert common.altcode("A02014B400000000000000F9D514") == 32300
def test_modes_idcode():
assert common.idcode("A800292DFFBBA9383FFCEB903D01") == "1346"
def test_graycode_to_altitude():
assert common.gray2alt("00000000010") == -1000
assert common.gray2alt("00000001010") == -500
assert common.gray2alt("00000011011") == -100
assert common.gray2alt("00000011010") == 0
assert common.gray2alt("00000011110") == 100
assert common.gray2alt("00000010011") == 600
assert common.gray2alt("00000110010") == 1000
assert common.gray2alt("00001001001") == 5800
assert common.gray2alt("00011100100") == 10300
assert common.gray2alt("01100011010") == 32000
assert common.gray2alt("01110000100") == 46300
assert common.gray2alt("01010101100") == 50200
assert common.gray2alt("11011110100") == 73200
assert common.gray2alt("10000000011") == 126600
assert common.gray2alt("10000000001") == 126700

62
tests/test_common.py Normal file
View File

@@ -0,0 +1,62 @@
from pyModeS.decoder import common
def test_conversions():
assert common.hex2bin("6E406B") == "011011100100000001101011"
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 parity == 11160538
def test_icao():
assert common.icao("8D406B902015A678D4D220AA4BDA") == "406B90"
assert common.icao("A0001839CA3800315800007448D9") == "400940"
assert common.icao("A000139381951536E024D4CCF6B5") == "3C4DD2"
assert common.icao("A000029CFFBAA11E2004727281F1") == "4243D0"
def test_modes_altcode():
assert common.altcode("A02014B400000000000000F9D514") == 32300
def test_modes_idcode():
assert common.idcode("A800292DFFBBA9383FFCEB903D01") == "1346"
def test_graycode_to_altitude():
assert common.gray2alt("00000000010") == -1000
assert common.gray2alt("00000001010") == -500
assert common.gray2alt("00000011011") == -100
assert common.gray2alt("00000011010") == 0
assert common.gray2alt("00000011110") == 100
assert common.gray2alt("00000010011") == 600
assert common.gray2alt("00000110010") == 1000
assert common.gray2alt("00001001001") == 5800
assert common.gray2alt("00011100100") == 10300
assert common.gray2alt("01100011010") == 32000
assert common.gray2alt("01110000100") == 46300
assert common.gray2alt("01010101100") == 50200
assert common.gray2alt("11011110100") == 73200
assert common.gray2alt("10000000011") == 126600
assert common.gray2alt("10000000001") == 126700

View File

@@ -1,64 +0,0 @@
from pyModeS import py_common
def test_conversions():
assert py_common.hex2bin("6E") == "01101110"
assert py_common.bin2hex("01101110") == "6E"
assert py_common.bin2hex("1101110") == "6E"
def test_crc_decode():
assert py_common.crc_legacy("8D406B902015A678D4D220AA4BDA") == 0
assert py_common.crc("8D406B902015A678D4D220AA4BDA") == 0
assert py_common.crc("8d8960ed58bf053cf11bc5932b7d") == 0
assert py_common.crc("8d45cab390c39509496ca9a32912") == 0
assert py_common.crc("8d49d3d4e1089d00000000744c3b") == 0
assert py_common.crc("8d74802958c904e6ef4ba0184d5c") == 0
assert py_common.crc("8d4400cd9b0000b4f87000e71a10") == 0
assert py_common.crc("8d4065de58a1054a7ef0218e226a") == 0
assert py_common.crc("c80b2dca34aa21dd821a04cb64d4") == 10719924
assert py_common.crc("a800089d8094e33a6004e4b8a522") == 4805588
assert py_common.crc("a8000614a50b6d32bed000bbe0ed") == 5659991
assert py_common.crc("a0000410bc900010a40000f5f477") == 11727682
assert py_common.crc("8d4ca251204994b1c36e60a5343d") == 16
assert py_common.crc("b0001718c65632b0a82040715b65") == 353333
def test_crc_encode():
parity = py_common.crc("8D406B902015A678D4D220AA4BDA", encode=True)
assert parity == 11160538
def test_icao():
assert py_common.icao("8D406B902015A678D4D220AA4BDA") == "406B90"
assert py_common.icao("A0001839CA3800315800007448D9") == "400940"
assert py_common.icao("A000139381951536E024D4CCF6B5") == "3C4DD2"
assert py_common.icao("A000029CFFBAA11E2004727281F1") == "4243D0"
def test_modes_altcode():
assert py_common.altcode("A02014B400000000000000F9D514") == 32300
def test_modes_idcode():
assert py_common.idcode("A800292DFFBBA9383FFCEB903D01") == "1346"
def test_graycode_to_altitude():
assert py_common.gray2alt("00000000010") == -1000
assert py_common.gray2alt("00000001010") == -500
assert py_common.gray2alt("00000011011") == -100
assert py_common.gray2alt("00000011010") == 0
assert py_common.gray2alt("00000011110") == 100
assert py_common.gray2alt("00000010011") == 600
assert py_common.gray2alt("00000110010") == 1000
assert py_common.gray2alt("00001001001") == 5800
assert py_common.gray2alt("00011100100") == 10300
assert py_common.gray2alt("01100011010") == 32000
assert py_common.gray2alt("01110000100") == 46300
assert py_common.gray2alt("01010101100") == 50200
assert py_common.gray2alt("11011110100") == 73200
assert py_common.gray2alt("10000000011") == 126600
assert py_common.gray2alt("10000000001") == 126700

View File

@@ -1,22 +0,0 @@
from pyModeS import surv
def test_fs():
assert surv.fs("2A00516D492B80")[0] == 2
def test_dr():
assert surv.dr("2A00516D492B80")[0] == 0
def test_um():
assert surv.um("200CBE4ED80137")[0] == 9
assert surv.um("200CBE4ED80137")[1] == 1
def test_identity():
assert surv.identity("2A00516D492B80") == "0356"
def test_altitude():
assert surv.altitude("20001718029FCD") == 36000