Compare commits
154 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
070dc80bf4 | ||
|
|
8d9a8df9d9 | ||
|
|
458a78c30b | ||
|
|
57aa7a7d9c | ||
|
|
8e2051af68 | ||
|
|
50864b56aa | ||
|
|
8403cb61e4 | ||
|
|
a5b8d670d3 | ||
|
|
4e19ecdfa9 | ||
|
|
0d7a310007 | ||
|
|
66c29840b0 | ||
|
|
99dc2a4f40 | ||
|
|
39936e4472 | ||
|
|
144ee1710d | ||
|
|
09ed997f91 | ||
|
|
e437931254 | ||
|
|
72fcc794e7 | ||
|
|
ff12d819da | ||
|
|
dc9d7ea91a | ||
|
|
5bc11cd78c | ||
|
|
9e6f9c3ffe | ||
|
|
e1017bc2e5 | ||
|
|
ebe1f76cd9 | ||
|
|
8830ef3dfe | ||
|
|
d999851da5 | ||
|
|
6d0406c6b9 | ||
|
|
6140f84058 | ||
|
|
a8f3b9c811 | ||
|
|
8f098671a0 | ||
|
|
4cf99e8927 | ||
|
|
5a703a0d3c | ||
|
|
90f35a8f29 | ||
|
|
87e6f50486 | ||
|
|
0667c1b3c3 | ||
|
|
9d82f9b9c9 | ||
|
|
51222372b1 | ||
|
|
d3607a16c1 | ||
|
|
6a427c08b5 | ||
|
|
afd53af87b | ||
|
|
6b4a5bdaf3 | ||
|
|
3e0d7ceec5 | ||
|
|
130491e641 | ||
|
|
1f620d6534 | ||
|
|
b4d4ef8f1f | ||
|
|
bb8f83e832 | ||
|
|
b90932e51d | ||
|
|
b36540e2a1 | ||
|
|
c02414417d | ||
|
|
13f4fe77bb | ||
|
|
401f3f5e95 | ||
|
|
73c3a75234 | ||
|
|
06941bac2e | ||
|
|
6e33ac0288 | ||
|
|
65ce1a62c4 | ||
|
|
5dc74a5548 | ||
|
|
e7edba9e25 | ||
|
|
ce5adbafe5 | ||
|
|
b52431ca51 | ||
|
|
00f04a8886 | ||
|
|
0eb2d1f6a2 | ||
|
|
26ef5d3ad9 | ||
|
|
b90c2e740d | ||
|
|
e39baf38ed | ||
|
|
b9471d7fcb | ||
|
|
a350050e6e | ||
|
|
01574a9f01 | ||
|
|
cdb7aef82e | ||
|
|
a0a8c9b2f7 | ||
|
|
0b2648bfe0 | ||
|
|
69ce39ab39 | ||
|
|
83e22892ba | ||
|
|
7434fc9ed3 | ||
|
|
7260bff7e9 | ||
|
|
dd1fd596f8 | ||
|
|
b9089d55d2 | ||
|
|
6ab147bffe | ||
|
|
a308b9a7e0 | ||
|
|
96f49a00e4 | ||
|
|
b8f8f4dbc0 | ||
|
|
a4ce3bfaf1 | ||
|
|
3bb8c361e9 | ||
|
|
89e67fae31 | ||
|
|
50cef7d424 | ||
|
|
20801883d5 | ||
|
|
56d70b03d2 | ||
|
|
47de4d1163 | ||
|
|
2978329d03 | ||
|
|
4471c836d9 | ||
|
|
d16484bf72 | ||
|
|
436e9ce2c8 | ||
|
|
4a6341bcc0 | ||
|
|
90e60a74b5 | ||
|
|
f65ffd13f7 | ||
|
|
a3e5e7e141 | ||
|
|
668d6580d0 | ||
|
|
eacc828b75 | ||
|
|
4ba900f500 | ||
|
|
35c0bfa513 | ||
|
|
beb50fc788 | ||
|
|
fc135eacef | ||
|
|
84c6255567 | ||
|
|
224a0b8a67 | ||
|
|
c3976f1ca3 | ||
|
|
dd3a869c08 | ||
|
|
7e966090ab | ||
|
|
7a4c465f7d | ||
|
|
0ced97e3eb | ||
|
|
2bdd638cef | ||
|
|
93fc536926 | ||
|
|
1f15a953e3 | ||
|
|
0d7f628f3c | ||
|
|
7348a10f1b | ||
|
|
c9d2c4c6bc | ||
|
|
0c5539be3a | ||
|
|
1cf4d37e56 | ||
|
|
2fc376abae | ||
|
|
9c660578ad | ||
|
|
ba84cd7c39 | ||
|
|
2085a2c432 | ||
|
|
4126dedd19 | ||
|
|
7892aac4de | ||
|
|
7dcff01910 | ||
|
|
228306dd5f | ||
|
|
1c6b39322f | ||
|
|
9dde1b63d6 | ||
|
|
560b737739 | ||
|
|
d50f05d00d | ||
|
|
11a560dbb5 | ||
|
|
b51facbdc6 | ||
|
|
d77d1f7f6b | ||
|
|
5286355bf6 | ||
|
|
fe9e033a64 | ||
|
|
57307109e6 | ||
|
|
dc8f194e6e | ||
|
|
6571fe6fc0 | ||
|
|
aa64d4e7a9 | ||
|
|
db57d7419f | ||
|
|
ed18352c0c | ||
|
|
a75d6fd050 | ||
|
|
0ff628bb8e | ||
|
|
ea7653ef79 | ||
|
|
2046b1de07 | ||
|
|
768b80df8e | ||
|
|
e52d43f963 | ||
|
|
b60b31d4fb | ||
|
|
cb67c63326 | ||
|
|
bddaf9aec6 | ||
|
|
00fc1475ff | ||
|
|
489c405de0 | ||
|
|
ec2721cfdc | ||
|
|
2f13524a7c | ||
|
|
fb24d4f25c | ||
|
|
9ad535dc93 | ||
|
|
9a89766766 |
12
.coveragerc
Normal file
12
.coveragerc
Normal file
@@ -0,0 +1,12 @@
|
||||
[run]
|
||||
branch = True
|
||||
include = */pyModeS/*
|
||||
omit = *tests*
|
||||
|
||||
[report]
|
||||
exclude_lines =
|
||||
coverage: ignore
|
||||
raise NotImplementedError
|
||||
if TYPE_CHECKING:
|
||||
|
||||
ignore_errors = True
|
||||
11
.github/dependabot.yml
vendored
Normal file
11
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
67
.github/workflows/pypi-publish.yml
vendored
Normal file
67
.github/workflows/pypi-publish.yml
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
# This workflows will upload a Python Package using Twine when a release is created
|
||||
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
|
||||
|
||||
name: publish
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
POETRY_VERSION: "1.3.1"
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [windows-latest, ubuntu-latest, macos-latest]
|
||||
python-version: ["3.8", "3.9", "3.10", "3.11"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Add poetry to windows path
|
||||
if: "startsWith(runner.os, 'windows')"
|
||||
run: |
|
||||
echo "C:\Users\runneradmin\.local\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||
|
||||
- name: Install and configure Poetry
|
||||
uses: snok/install-poetry@v1.3.3
|
||||
with:
|
||||
version: ${{ env.POETRY_VERSION }}
|
||||
virtualenvs-create: true
|
||||
virtualenvs-in-project: true
|
||||
|
||||
- name: Display Python version
|
||||
run: poetry run python -c "import sys; print(sys.version)"
|
||||
|
||||
- name: Build packages
|
||||
run: poetry build
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
poetry run pip install --upgrade pip
|
||||
poetry run pip install twine
|
||||
|
||||
- name: Build and publish
|
||||
env:
|
||||
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
|
||||
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
|
||||
run: |
|
||||
poetry run twine upload dist/*.whl
|
||||
|
||||
- name: Build and publish (source)
|
||||
if: ${{ startsWith(runner.os, 'windows') && matrix.python-version == '3.11' }}
|
||||
env:
|
||||
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
|
||||
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
|
||||
run: |
|
||||
poetry run twine upload dist/*.tar.gz
|
||||
70
.github/workflows/run-tests.yml
vendored
Normal file
70
.github/workflows/run-tests.yml
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
name: tests
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request_target:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
POETRY_VERSION: "1.3.1"
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
python-version: ["3.8", "3.9", "3.10", "3.11"]
|
||||
|
||||
env:
|
||||
PYTHON_VERSION: ${{ matrix.python-version }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
# virtualenv cache should depends on OS, Python version and `poetry.lock` (and optionally workflow files).
|
||||
- name: Cache Packages
|
||||
uses: actions/cache@v3
|
||||
if: ${{ !startsWith(runner.os, 'windows') }}
|
||||
with:
|
||||
path: |
|
||||
~/.local
|
||||
.venv
|
||||
key: poetry-${{ runner.os }}-${{ env.PYTHON_VERSION }}-${{ hashFiles('**/poetry.lock') }}
|
||||
|
||||
- name: Add poetry to windows path
|
||||
if: "startsWith(runner.os, 'windows')"
|
||||
run: |
|
||||
echo "C:\Users\runneradmin\.local\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||
|
||||
- name: Install and configure Poetry
|
||||
uses: snok/install-poetry@v1.3.3
|
||||
with:
|
||||
version: ${{ env.POETRY_VERSION }}
|
||||
virtualenvs-create: true
|
||||
virtualenvs-in-project: true
|
||||
|
||||
- name: Display Python version
|
||||
run: poetry run python -c "import sys; print(sys.version)"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
poetry install
|
||||
|
||||
- name: Type checking
|
||||
run: |
|
||||
poetry run mypy pyModeS
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
poetry run pytest tests --cov --cov-report term-missing
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
env_vars: PYTHON_VERSION
|
||||
26
Makefile
Normal file
26
Makefile
Normal file
@@ -0,0 +1,26 @@
|
||||
install:
|
||||
pip install . --upgrade
|
||||
|
||||
uninstall:
|
||||
pip uninstall pyModeS -y
|
||||
|
||||
ext:
|
||||
python setup.py build_ext --inplace
|
||||
|
||||
test:
|
||||
make clean
|
||||
@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:
|
||||
find pyModeS -type f -name '*.c' -delete
|
||||
find pyModeS -type f -name '*.so' -delete
|
||||
find . | grep -E "(__pycache__|\.pyc|\.pyo$$)" | xargs rm -rf
|
||||
rm -rf *.egg-info
|
||||
rm -rf .pytest_cache
|
||||
rm -rf build/*
|
||||
155
README.rst
155
README.rst
@@ -1,6 +1,42 @@
|
||||
The Python ADS-B/Mode-S Decoder
|
||||
===============================
|
||||
|
||||
PyModeS is a Python library designed to decode Mode-S (including ADS-B) messages. 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::
|
||||
|
||||
@article{sun2019pymodes,
|
||||
@@ -14,40 +50,6 @@ 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
|
||||
-----------
|
||||
@@ -55,32 +57,50 @@ Check out and contribute to this open-source project at:
|
||||
https://github.com/junzis/pyModeS
|
||||
|
||||
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:
|
||||
http://pymodes.readthedocs.io
|
||||
https://mode-s.org/api
|
||||
|
||||
|
||||
|
||||
Install
|
||||
-------
|
||||
|
||||
The pyModeS can be installed with extra option ``[all]`` in order to install dependencies ``pyzmq`` and ``pyrtlsdr`` automatically.
|
||||
Basic installation
|
||||
-------------------
|
||||
|
||||
Installation examples::
|
||||
|
||||
# stable version, basic
|
||||
# stable version
|
||||
pip install pyModeS
|
||||
|
||||
# stable version, including dependencies for streamer and rtlsdr
|
||||
pip install pyModeS[all]
|
||||
# conda (compiled) version
|
||||
conda install -c conda-forge pymodes
|
||||
|
||||
# development version, basic
|
||||
# development version
|
||||
pip install git+https://github.com/junzis/pyModeS
|
||||
|
||||
# development version, including dependencies for streamer and rtlsdr
|
||||
pip install git+https://github.com/junzis/pyModeS#egg=pyModeS[all]
|
||||
|
||||
Dependencies ``numpy``, and ``pyzmq`` 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
|
||||
pip install pyModeS
|
||||
|
||||
# development version
|
||||
git clone https://github.com/junzis/pyModeS
|
||||
cd pyModeS
|
||||
poetry install -E rtlsdr
|
||||
|
||||
|
||||
View live traffic (modeslive)
|
||||
@@ -106,7 +126,7 @@ General usage::
|
||||
Live with RTL-SDR
|
||||
*******************
|
||||
|
||||
If you have an RTL-SDR receiver plugged to the computer, you can connect it with ``rtlsdr`` source switch, shown as follows::
|
||||
If you have an RTL-SDR receiver connected to your computer, you can use the ``rtlsdr`` source switch (require ``pyrtlsdr`` package), with command::
|
||||
|
||||
$ modeslive --source rtlsdr
|
||||
|
||||
@@ -147,7 +167,7 @@ Common functions
|
||||
pms.hex2bin(str) # Convert hexadecimal string to binary string
|
||||
pms.bin2int(str) # Convert binary string to integer
|
||||
pms.hex2int(str) # Convert hexadecimal string to integer
|
||||
pms.gray2int(str) # Convert grey code to interger
|
||||
pms.gray2int(str) # Convert grey code to integer
|
||||
|
||||
|
||||
Core functions for ADS-B decoding
|
||||
@@ -161,7 +181,7 @@ Core functions for ADS-B decoding
|
||||
# Typecode 1-4
|
||||
pms.adsb.callsign(msg)
|
||||
|
||||
# Typecode 5-8 (surface), 9-18 (airborne, barometric height), and 9-18 (airborne, GNSS height)
|
||||
# Typecode 5-8 (surface), 9-18 (airborne, barometric height), and 20-22 (airborne, GNSS height)
|
||||
pms.adsb.position(msg_even, msg_odd, t_even, t_odd, lat_ref=None, lon_ref=None)
|
||||
pms.adsb.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)
|
||||
@@ -255,8 +275,20 @@ Mode-S Enhanced Surveillance (EHS)
|
||||
pms.commb.vr60ins(msg) # Inertial vertical speed (ft/min)
|
||||
|
||||
|
||||
Meteorological routine air report (MRAR) [Experimental]
|
||||
********************************************************
|
||||
Meteorological reports [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
|
||||
|
||||
@@ -267,8 +299,8 @@ Meteorological routine air report (MRAR) [Experimental]
|
||||
pms.commb.hum44(msg) # Humidity (%)
|
||||
|
||||
|
||||
Meteorological hazard air report (MHR) [Experimental]
|
||||
*******************************************************
|
||||
Meteorological hazard air report (MHR)
|
||||
+++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
.. code:: python
|
||||
|
||||
@@ -327,8 +359,19 @@ Here is an example:
|
||||
|
||||
Unit test
|
||||
---------
|
||||
To perform unit tests. First, install ``tox`` through pip. Then, run the following commands:
|
||||
To perform unit tests, ``pytest`` must be install first.
|
||||
|
||||
.. code:: bash
|
||||
Build Cython extensions
|
||||
::
|
||||
|
||||
$ tox
|
||||
$ make ext
|
||||
|
||||
Run unit tests
|
||||
::
|
||||
|
||||
$ make test
|
||||
|
||||
Clean build files
|
||||
::
|
||||
|
||||
$ make clean
|
||||
|
||||
69
build.py
Normal file
69
build.py
Normal file
@@ -0,0 +1,69 @@
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
from distutils.command import build_ext
|
||||
from distutils.core import Distribution, Extension
|
||||
|
||||
# import pip
|
||||
from Cython.Build import cythonize
|
||||
|
||||
|
||||
def build() -> None:
|
||||
compile_args = []
|
||||
|
||||
if sys.platform == "linux":
|
||||
compile_args += ["-Wno-pointer-sign", "-Wno-unused-variable"]
|
||||
|
||||
extensions = [
|
||||
Extension(
|
||||
"pyModeS.c_common",
|
||||
["pyModeS/c_common.pyx"],
|
||||
extra_compile_args=compile_args,
|
||||
),
|
||||
Extension(
|
||||
"pyModeS.decoder.flarm.decode",
|
||||
[
|
||||
"pyModeS/decoder/flarm/decode.pyx",
|
||||
"pyModeS/decoder/flarm/core.c",
|
||||
],
|
||||
extra_compile_args=compile_args,
|
||||
include_dirs=["pyModeS/decoder/flarm"],
|
||||
),
|
||||
# Extension(
|
||||
# "pyModeS.extra.demod2400.core",
|
||||
# [
|
||||
# "pyModeS/extra/demod2400/core.pyx",
|
||||
# "pyModeS/extra/demod2400/demod2400.c",
|
||||
# ],
|
||||
# extra_compile_args=compile_args,
|
||||
# include_dirs=["pyModeS/extra/demod2400"],
|
||||
# libraries=["m"],
|
||||
# ),
|
||||
]
|
||||
|
||||
ext_modules = cythonize(
|
||||
extensions,
|
||||
compiler_directives={"binding": True, "language_level": 3},
|
||||
)
|
||||
|
||||
distribution = Distribution(
|
||||
{"name": "extended", "ext_modules": ext_modules}
|
||||
)
|
||||
distribution.package_dir = "extended" # type: ignore
|
||||
|
||||
cmd = build_ext.build_ext(distribution)
|
||||
cmd.verbose = True # type: ignore
|
||||
cmd.ensure_finalized() # type: ignore
|
||||
cmd.run()
|
||||
|
||||
# Copy built extensions back to the project
|
||||
for output in cmd.get_outputs():
|
||||
relative_extension = os.path.relpath(output, cmd.build_lib)
|
||||
shutil.copyfile(output, relative_extension)
|
||||
mode = os.stat(relative_extension).st_mode
|
||||
mode |= (mode & 0o444) >> 2
|
||||
os.chmod(relative_extension, mode)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
build()
|
||||
@@ -17,5 +17,5 @@ help:
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
rm -f source/pyModeS*.rst source/modules.rst
|
||||
sphinx-apidoc -f -e -M -o source/ ../pyModeS
|
||||
sphinx-apidoc -f -e -M -o source/ ../pyModeS ../pyModeS/decoder/ehs.py ../pyModeS/decoder/els.py ../pyModeS/streamer ../pyModeS/extra
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
17
doc/source/_templates/layout.html
Normal file
17
doc/source/_templates/layout.html
Normal file
@@ -0,0 +1,17 @@
|
||||
{% extends "!layout.html" %}
|
||||
|
||||
{% block footer %}
|
||||
{{ super() }}
|
||||
|
||||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-74700456-1"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
|
||||
gtag('config', 'UA-74700456-1');
|
||||
</script>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
@@ -14,19 +14,20 @@
|
||||
#
|
||||
import os
|
||||
import sys
|
||||
sys.path.insert(0, os.path.abspath('../..'))
|
||||
|
||||
sys.path.insert(0, os.path.abspath("../.."))
|
||||
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = 'pyModeS'
|
||||
copyright = '2019, Junzi Sun'
|
||||
author = 'Junzi Sun'
|
||||
project = "pyModeS"
|
||||
copyright = "2019, Junzi Sun"
|
||||
author = "Junzi Sun"
|
||||
|
||||
# The short X.Y version
|
||||
version = ''
|
||||
version = ""
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = ''
|
||||
release = ""
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
@@ -39,26 +40,24 @@ release = ''
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinx.ext.todo',
|
||||
'sphinx.ext.coverage',
|
||||
'sphinx.ext.mathjax',
|
||||
'sphinx.ext.viewcode',
|
||||
'sphinx.ext.githubpages',
|
||||
'sphinx.ext.napoleon',
|
||||
"sphinx.ext.autodoc",
|
||||
"sphinx.ext.mathjax",
|
||||
"sphinx.ext.viewcode",
|
||||
"sphinx.ext.githubpages",
|
||||
"sphinx.ext.napoleon",
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
# templates_path = ['']
|
||||
templates_path = ["_templates"]
|
||||
|
||||
# The suffix(es) of source filenames.
|
||||
# You can specify multiple suffix as a list of string:
|
||||
#
|
||||
# source_suffix = ['.rst', '.md']
|
||||
source_suffix = '.rst'
|
||||
source_suffix = ".rst"
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
master_doc = "index"
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
@@ -82,6 +81,10 @@ pygments_style = None
|
||||
# a list of builtin themes.
|
||||
#
|
||||
# html_theme = 'alabaster'
|
||||
html_theme = "neo_rtd_theme"
|
||||
import sphinx_theme
|
||||
|
||||
html_theme_path = [sphinx_theme.get_html_theme_path()]
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
@@ -108,7 +111,7 @@ pygments_style = None
|
||||
# -- Options for HTMLHelp output ---------------------------------------------
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'pyModeSdoc'
|
||||
htmlhelp_basename = "pyModeSdoc"
|
||||
|
||||
|
||||
# -- Options for LaTeX output ------------------------------------------------
|
||||
@@ -117,15 +120,12 @@ latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#
|
||||
# 'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#
|
||||
# 'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#
|
||||
# 'preamble': '',
|
||||
|
||||
# Latex figure (float) alignment
|
||||
#
|
||||
# 'figure_align': 'htbp',
|
||||
@@ -135,8 +135,7 @@ latex_elements = {
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
(master_doc, 'pyModeS.tex', 'pyModeS Documentation',
|
||||
'Junzi Sun', 'manual'),
|
||||
(master_doc, "pyModeS.tex", "pyModeS Documentation", "Junzi Sun", "manual")
|
||||
]
|
||||
|
||||
|
||||
@@ -144,10 +143,7 @@ latex_documents = [
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
(master_doc, 'pymodes', 'pyModeS Documentation',
|
||||
[author], 1)
|
||||
]
|
||||
man_pages = [(master_doc, "pymodes", "pyModeS Documentation", [author], 1)]
|
||||
|
||||
|
||||
# -- Options for Texinfo output ----------------------------------------------
|
||||
@@ -156,9 +152,15 @@ man_pages = [
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(master_doc, 'pyModeS', 'pyModeS Documentation',
|
||||
author, 'pyModeS', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
(
|
||||
master_doc,
|
||||
"pyModeS",
|
||||
"pyModeS Documentation",
|
||||
author,
|
||||
"pyModeS",
|
||||
"One line description of project.",
|
||||
"Miscellaneous",
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
@@ -177,7 +179,7 @@ epub_title = project
|
||||
# epub_uid = ''
|
||||
|
||||
# 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 -------------------------------------------------
|
||||
|
||||
@@ -9,11 +9,49 @@ Welcome to pyModeS documentation!
|
||||
The source code can be found at: https://github.com/junzis/pyModeS
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 3
|
||||
:caption: Core modules
|
||||
:maxdepth: 2
|
||||
|
||||
pyModeS.decoder.adsb
|
||||
pyModeS.decoder.commb
|
||||
|
||||
|
||||
.. toctree::
|
||||
:caption: ADS-B messages
|
||||
:maxdepth: 2
|
||||
|
||||
pyModeS.decoder.bds.bds05
|
||||
pyModeS.decoder.bds.bds06
|
||||
pyModeS.decoder.bds.bds08
|
||||
pyModeS.decoder.bds.bds09
|
||||
|
||||
|
||||
.. toctree::
|
||||
:caption: ELS - elementary surveillance
|
||||
:maxdepth: 2
|
||||
|
||||
pyModeS.decoder.bds.bds10
|
||||
pyModeS.decoder.bds.bds17
|
||||
pyModeS.decoder.bds.bds20
|
||||
pyModeS.decoder.bds.bds30
|
||||
|
||||
|
||||
.. toctree::
|
||||
:caption: EHS - enhanced surveillance
|
||||
:maxdepth: 2
|
||||
|
||||
pyModeS.decoder.bds.bds40
|
||||
pyModeS.decoder.bds.bds50
|
||||
pyModeS.decoder.bds.bds60
|
||||
|
||||
|
||||
.. toctree::
|
||||
:caption: MRAR / MHR
|
||||
:maxdepth: 2
|
||||
|
||||
pyModeS.decoder.bds.bds44
|
||||
pyModeS.decoder.bds.bds45
|
||||
|
||||
pyModeS.decoder
|
||||
pyModeS.streamer
|
||||
pyModeS.extra
|
||||
|
||||
|
||||
----
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=.
|
||||
set BUILDDIR=_build
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.http://sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
||||
|
||||
:end
|
||||
popd
|
||||
7
doc/source/pyModeS.c_common.rst
Normal file
7
doc/source/pyModeS.c_common.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
pyModeS.c\_common module
|
||||
========================
|
||||
|
||||
.. automodule:: pyModeS.c_common
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
doc/source/pyModeS.common.rst
Normal file
7
doc/source/pyModeS.common.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
pyModeS.common module
|
||||
=====================
|
||||
|
||||
.. automodule:: pyModeS.common
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -2,6 +2,6 @@ pyModeS.decoder.acas module
|
||||
===========================
|
||||
|
||||
.. automodule:: pyModeS.decoder.acas
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
@@ -2,6 +2,6 @@ pyModeS.decoder.adsb module
|
||||
===========================
|
||||
|
||||
.. automodule:: pyModeS.decoder.adsb
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
@@ -2,6 +2,6 @@ pyModeS.decoder.allcall module
|
||||
==============================
|
||||
|
||||
.. automodule:: pyModeS.decoder.allcall
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
@@ -2,6 +2,6 @@ pyModeS.decoder.bds.bds05 module
|
||||
================================
|
||||
|
||||
.. automodule:: pyModeS.decoder.bds.bds05
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
@@ -2,6 +2,6 @@ pyModeS.decoder.bds.bds06 module
|
||||
================================
|
||||
|
||||
.. automodule:: pyModeS.decoder.bds.bds06
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
@@ -2,6 +2,6 @@ pyModeS.decoder.bds.bds08 module
|
||||
================================
|
||||
|
||||
.. automodule:: pyModeS.decoder.bds.bds08
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
@@ -2,6 +2,6 @@ pyModeS.decoder.bds.bds09 module
|
||||
================================
|
||||
|
||||
.. automodule:: pyModeS.decoder.bds.bds09
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
@@ -2,6 +2,6 @@ pyModeS.decoder.bds.bds10 module
|
||||
================================
|
||||
|
||||
.. automodule:: pyModeS.decoder.bds.bds10
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
@@ -2,6 +2,6 @@ pyModeS.decoder.bds.bds17 module
|
||||
================================
|
||||
|
||||
.. automodule:: pyModeS.decoder.bds.bds17
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
@@ -2,6 +2,6 @@ pyModeS.decoder.bds.bds20 module
|
||||
================================
|
||||
|
||||
.. automodule:: pyModeS.decoder.bds.bds20
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
@@ -2,6 +2,6 @@ pyModeS.decoder.bds.bds30 module
|
||||
================================
|
||||
|
||||
.. automodule:: pyModeS.decoder.bds.bds30
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
@@ -2,6 +2,6 @@ pyModeS.decoder.bds.bds40 module
|
||||
================================
|
||||
|
||||
.. automodule:: pyModeS.decoder.bds.bds40
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
@@ -2,6 +2,6 @@ pyModeS.decoder.bds.bds44 module
|
||||
================================
|
||||
|
||||
.. automodule:: pyModeS.decoder.bds.bds44
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
@@ -2,6 +2,6 @@ pyModeS.decoder.bds.bds45 module
|
||||
================================
|
||||
|
||||
.. automodule:: pyModeS.decoder.bds.bds45
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
@@ -2,6 +2,6 @@ pyModeS.decoder.bds.bds50 module
|
||||
================================
|
||||
|
||||
.. automodule:: pyModeS.decoder.bds.bds50
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
@@ -2,6 +2,6 @@ pyModeS.decoder.bds.bds53 module
|
||||
================================
|
||||
|
||||
.. automodule:: pyModeS.decoder.bds.bds53
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
@@ -2,6 +2,6 @@ pyModeS.decoder.bds.bds60 module
|
||||
================================
|
||||
|
||||
.. automodule:: pyModeS.decoder.bds.bds60
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
@@ -2,14 +2,15 @@ pyModeS.decoder.bds package
|
||||
===========================
|
||||
|
||||
.. automodule:: pyModeS.decoder.bds
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Submodules
|
||||
----------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 4
|
||||
|
||||
pyModeS.decoder.bds.bds05
|
||||
pyModeS.decoder.bds.bds06
|
||||
@@ -25,4 +26,3 @@ Submodules
|
||||
pyModeS.decoder.bds.bds50
|
||||
pyModeS.decoder.bds.bds53
|
||||
pyModeS.decoder.bds.bds60
|
||||
|
||||
|
||||
@@ -2,6 +2,6 @@ pyModeS.decoder.commb module
|
||||
============================
|
||||
|
||||
.. automodule:: pyModeS.decoder.commb
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
pyModeS.decoder.common module
|
||||
=============================
|
||||
|
||||
.. automodule:: pyModeS.decoder.common
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
pyModeS.decoder.ehs module
|
||||
==========================
|
||||
|
||||
.. automodule:: pyModeS.decoder.ehs
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
pyModeS.decoder.els module
|
||||
==========================
|
||||
|
||||
.. automodule:: pyModeS.decoder.els
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -2,29 +2,28 @@ pyModeS.decoder package
|
||||
=======================
|
||||
|
||||
.. automodule:: pyModeS.decoder
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Subpackages
|
||||
-----------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 4
|
||||
|
||||
pyModeS.decoder.bds
|
||||
pyModeS.decoder.bds
|
||||
|
||||
Submodules
|
||||
----------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 4
|
||||
|
||||
pyModeS.decoder.acas
|
||||
pyModeS.decoder.adsb
|
||||
pyModeS.decoder.allcall
|
||||
pyModeS.decoder.commb
|
||||
pyModeS.decoder.common
|
||||
pyModeS.decoder.ehs
|
||||
pyModeS.decoder.els
|
||||
pyModeS.decoder.surv
|
||||
pyModeS.decoder.uncertainty
|
||||
|
||||
pyModeS.decoder.uplink
|
||||
|
||||
@@ -2,6 +2,6 @@ pyModeS.decoder.surv module
|
||||
===========================
|
||||
|
||||
.. automodule:: pyModeS.decoder.surv
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
@@ -2,6 +2,6 @@ pyModeS.decoder.uncertainty module
|
||||
==================================
|
||||
|
||||
.. automodule:: pyModeS.decoder.uncertainty
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
7
doc/source/pyModeS.decoder.uplink.rst
Normal file
7
doc/source/pyModeS.decoder.uplink.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
pyModeS.decoder.uplink module
|
||||
=============================
|
||||
|
||||
.. automodule:: pyModeS.decoder.uplink
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
pyModeS.extra.aero module
|
||||
=========================
|
||||
|
||||
.. automodule:: pyModeS.extra.aero
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,16 +0,0 @@
|
||||
pyModeS.extra package
|
||||
=====================
|
||||
|
||||
.. automodule:: pyModeS.extra
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Submodules
|
||||
----------
|
||||
|
||||
.. toctree::
|
||||
|
||||
pyModeS.extra.aero
|
||||
pyModeS.extra.tcpclient
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
pyModeS.extra.tcpclient module
|
||||
==============================
|
||||
|
||||
.. automodule:: pyModeS.extra.tcpclient
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -2,16 +2,23 @@ pyModeS package
|
||||
===============
|
||||
|
||||
.. automodule:: pyModeS
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Subpackages
|
||||
-----------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 4
|
||||
|
||||
pyModeS.decoder
|
||||
pyModeS.extra
|
||||
pyModeS.streamer
|
||||
pyModeS.decoder
|
||||
|
||||
Submodules
|
||||
----------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 4
|
||||
|
||||
pyModeS.c_common
|
||||
pyModeS.common
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
pyModeS.streamer package
|
||||
========================
|
||||
|
||||
.. automodule:: pyModeS.streamer
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Submodules
|
||||
----------
|
||||
|
||||
.. toctree::
|
||||
|
||||
pyModeS.streamer.screen
|
||||
pyModeS.streamer.stream
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
pyModeS.streamer.screen module
|
||||
==============================
|
||||
|
||||
.. automodule:: pyModeS.streamer.screen
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
@@ -1,7 +0,0 @@
|
||||
pyModeS.streamer.stream module
|
||||
==============================
|
||||
|
||||
.. automodule:: pyModeS.streamer.stream
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
0
doc/warnings
Normal file
0
doc/warnings
Normal file
793
poetry.lock
generated
Normal file
793
poetry.lock
generated
Normal file
@@ -0,0 +1,793 @@
|
||||
# This file is automatically @generated by Poetry 1.5.0 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "black"
|
||||
version = "22.12.0"
|
||||
description = "The uncompromising code formatter."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "black-22.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d"},
|
||||
{file = "black-22.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351"},
|
||||
{file = "black-22.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f"},
|
||||
{file = "black-22.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4"},
|
||||
{file = "black-22.12.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2"},
|
||||
{file = "black-22.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350"},
|
||||
{file = "black-22.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d"},
|
||||
{file = "black-22.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc"},
|
||||
{file = "black-22.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320"},
|
||||
{file = "black-22.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148"},
|
||||
{file = "black-22.12.0-py3-none-any.whl", hash = "sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf"},
|
||||
{file = "black-22.12.0.tar.gz", hash = "sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=8.0.0"
|
||||
mypy-extensions = ">=0.4.3"
|
||||
pathspec = ">=0.9.0"
|
||||
platformdirs = ">=2"
|
||||
tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""}
|
||||
typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
|
||||
|
||||
[package.extras]
|
||||
colorama = ["colorama (>=0.4.3)"]
|
||||
d = ["aiohttp (>=3.7.4)"]
|
||||
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
|
||||
uvloop = ["uvloop (>=0.15.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2023.5.7"
|
||||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"},
|
||||
{file = "certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cffi"
|
||||
version = "1.15.1"
|
||||
description = "Foreign Function Interface for Python calling C code."
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"},
|
||||
{file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"},
|
||||
{file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"},
|
||||
{file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"},
|
||||
{file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"},
|
||||
{file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"},
|
||||
{file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"},
|
||||
{file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"},
|
||||
{file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"},
|
||||
{file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"},
|
||||
{file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"},
|
||||
{file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"},
|
||||
{file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"},
|
||||
{file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"},
|
||||
{file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"},
|
||||
{file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"},
|
||||
{file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"},
|
||||
{file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"},
|
||||
{file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"},
|
||||
{file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"},
|
||||
{file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"},
|
||||
{file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"},
|
||||
{file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"},
|
||||
{file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"},
|
||||
{file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"},
|
||||
{file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"},
|
||||
{file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"},
|
||||
{file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"},
|
||||
{file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"},
|
||||
{file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"},
|
||||
{file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"},
|
||||
{file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"},
|
||||
{file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"},
|
||||
{file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"},
|
||||
{file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"},
|
||||
{file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"},
|
||||
{file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"},
|
||||
{file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"},
|
||||
{file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"},
|
||||
{file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"},
|
||||
{file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"},
|
||||
{file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pycparser = "*"
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "3.1.0"
|
||||
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
|
||||
optional = false
|
||||
python-versions = ">=3.7.0"
|
||||
files = [
|
||||
{file = "charset-normalizer-3.1.0.tar.gz", hash = "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5"},
|
||||
{file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b"},
|
||||
{file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60"},
|
||||
{file = "charset_normalizer-3.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1"},
|
||||
{file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0"},
|
||||
{file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f"},
|
||||
{file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0"},
|
||||
{file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795"},
|
||||
{file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c"},
|
||||
{file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203"},
|
||||
{file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1"},
|
||||
{file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137"},
|
||||
{file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce"},
|
||||
{file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a"},
|
||||
{file = "charset_normalizer-3.1.0-cp310-cp310-win32.whl", hash = "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448"},
|
||||
{file = "charset_normalizer-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8"},
|
||||
{file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19"},
|
||||
{file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017"},
|
||||
{file = "charset_normalizer-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df"},
|
||||
{file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a"},
|
||||
{file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41"},
|
||||
{file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1"},
|
||||
{file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62"},
|
||||
{file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6"},
|
||||
{file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5"},
|
||||
{file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be"},
|
||||
{file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb"},
|
||||
{file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac"},
|
||||
{file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324"},
|
||||
{file = "charset_normalizer-3.1.0-cp311-cp311-win32.whl", hash = "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909"},
|
||||
{file = "charset_normalizer-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755"},
|
||||
{file = "charset_normalizer-3.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373"},
|
||||
{file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"},
|
||||
{file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9"},
|
||||
{file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f"},
|
||||
{file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28"},
|
||||
{file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d"},
|
||||
{file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d"},
|
||||
{file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d"},
|
||||
{file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6"},
|
||||
{file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84"},
|
||||
{file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c"},
|
||||
{file = "charset_normalizer-3.1.0-cp37-cp37m-win32.whl", hash = "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974"},
|
||||
{file = "charset_normalizer-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23"},
|
||||
{file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531"},
|
||||
{file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c"},
|
||||
{file = "charset_normalizer-3.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14"},
|
||||
{file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb"},
|
||||
{file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1"},
|
||||
{file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b"},
|
||||
{file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0"},
|
||||
{file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649"},
|
||||
{file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326"},
|
||||
{file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11"},
|
||||
{file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b"},
|
||||
{file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd"},
|
||||
{file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8"},
|
||||
{file = "charset_normalizer-3.1.0-cp38-cp38-win32.whl", hash = "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0"},
|
||||
{file = "charset_normalizer-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59"},
|
||||
{file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e"},
|
||||
{file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31"},
|
||||
{file = "charset_normalizer-3.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f"},
|
||||
{file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e"},
|
||||
{file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f"},
|
||||
{file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854"},
|
||||
{file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706"},
|
||||
{file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e"},
|
||||
{file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0"},
|
||||
{file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230"},
|
||||
{file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7"},
|
||||
{file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e"},
|
||||
{file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f"},
|
||||
{file = "charset_normalizer-3.1.0-cp39-cp39-win32.whl", hash = "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1"},
|
||||
{file = "charset_normalizer-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b"},
|
||||
{file = "charset_normalizer-3.1.0-py3-none-any.whl", hash = "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.1.3"
|
||||
description = "Composable command line interface toolkit"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
|
||||
{file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
||||
|
||||
[[package]]
|
||||
name = "codecov"
|
||||
version = "2.1.13"
|
||||
description = "Hosted coverage reports for GitHub, Bitbucket and Gitlab"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
files = [
|
||||
{file = "codecov-2.1.13-py2.py3-none-any.whl", hash = "sha256:c2ca5e51bba9ebb43644c43d0690148a55086f7f5e6fd36170858fa4206744d5"},
|
||||
{file = "codecov-2.1.13.tar.gz", hash = "sha256:2362b685633caeaf45b9951a9b76ce359cd3581dd515b430c6c3f5dfb4d92a8c"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
coverage = "*"
|
||||
requests = ">=2.7.9"
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
description = "Cross-platform colored terminal text."
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||
files = [
|
||||
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
|
||||
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "coverage"
|
||||
version = "7.2.5"
|
||||
description = "Code coverage measurement for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "coverage-7.2.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:883123d0bbe1c136f76b56276074b0c79b5817dd4238097ffa64ac67257f4b6c"},
|
||||
{file = "coverage-7.2.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d2fbc2a127e857d2f8898aaabcc34c37771bf78a4d5e17d3e1f5c30cd0cbc62a"},
|
||||
{file = "coverage-7.2.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f3671662dc4b422b15776cdca89c041a6349b4864a43aa2350b6b0b03bbcc7f"},
|
||||
{file = "coverage-7.2.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780551e47d62095e088f251f5db428473c26db7829884323e56d9c0c3118791a"},
|
||||
{file = "coverage-7.2.5-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:066b44897c493e0dcbc9e6a6d9f8bbb6607ef82367cf6810d387c09f0cd4fe9a"},
|
||||
{file = "coverage-7.2.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b9a4ee55174b04f6af539218f9f8083140f61a46eabcaa4234f3c2a452c4ed11"},
|
||||
{file = "coverage-7.2.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:706ec567267c96717ab9363904d846ec009a48d5f832140b6ad08aad3791b1f5"},
|
||||
{file = "coverage-7.2.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ae453f655640157d76209f42c62c64c4d4f2c7f97256d3567e3b439bd5c9b06c"},
|
||||
{file = "coverage-7.2.5-cp310-cp310-win32.whl", hash = "sha256:f81c9b4bd8aa747d417407a7f6f0b1469a43b36a85748145e144ac4e8d303cb5"},
|
||||
{file = "coverage-7.2.5-cp310-cp310-win_amd64.whl", hash = "sha256:dc945064a8783b86fcce9a0a705abd7db2117d95e340df8a4333f00be5efb64c"},
|
||||
{file = "coverage-7.2.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:40cc0f91c6cde033da493227797be2826cbf8f388eaa36a0271a97a332bfd7ce"},
|
||||
{file = "coverage-7.2.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a66e055254a26c82aead7ff420d9fa8dc2da10c82679ea850d8feebf11074d88"},
|
||||
{file = "coverage-7.2.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c10fbc8a64aa0f3ed136b0b086b6b577bc64d67d5581acd7cc129af52654384e"},
|
||||
{file = "coverage-7.2.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a22cbb5ede6fade0482111fa7f01115ff04039795d7092ed0db43522431b4f2"},
|
||||
{file = "coverage-7.2.5-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:292300f76440651529b8ceec283a9370532f4ecba9ad67d120617021bb5ef139"},
|
||||
{file = "coverage-7.2.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7ff8f3fb38233035028dbc93715551d81eadc110199e14bbbfa01c5c4a43f8d8"},
|
||||
{file = "coverage-7.2.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:a08c7401d0b24e8c2982f4e307124b671c6736d40d1c39e09d7a8687bddf83ed"},
|
||||
{file = "coverage-7.2.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ef9659d1cda9ce9ac9585c045aaa1e59223b143f2407db0eaee0b61a4f266fb6"},
|
||||
{file = "coverage-7.2.5-cp311-cp311-win32.whl", hash = "sha256:30dcaf05adfa69c2a7b9f7dfd9f60bc8e36b282d7ed25c308ef9e114de7fc23b"},
|
||||
{file = "coverage-7.2.5-cp311-cp311-win_amd64.whl", hash = "sha256:97072cc90f1009386c8a5b7de9d4fc1a9f91ba5ef2146c55c1f005e7b5c5e068"},
|
||||
{file = "coverage-7.2.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bebea5f5ed41f618797ce3ffb4606c64a5de92e9c3f26d26c2e0aae292f015c1"},
|
||||
{file = "coverage-7.2.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:828189fcdda99aae0d6bf718ea766b2e715eabc1868670a0a07bf8404bf58c33"},
|
||||
{file = "coverage-7.2.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e8a95f243d01ba572341c52f89f3acb98a3b6d1d5d830efba86033dd3687ade"},
|
||||
{file = "coverage-7.2.5-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8834e5f17d89e05697c3c043d3e58a8b19682bf365048837383abfe39adaed5"},
|
||||
{file = "coverage-7.2.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d1f25ee9de21a39b3a8516f2c5feb8de248f17da7eead089c2e04aa097936b47"},
|
||||
{file = "coverage-7.2.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1637253b11a18f453e34013c665d8bf15904c9e3c44fbda34c643fbdc9d452cd"},
|
||||
{file = "coverage-7.2.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8e575a59315a91ccd00c7757127f6b2488c2f914096077c745c2f1ba5b8c0969"},
|
||||
{file = "coverage-7.2.5-cp37-cp37m-win32.whl", hash = "sha256:509ecd8334c380000d259dc66feb191dd0a93b21f2453faa75f7f9cdcefc0718"},
|
||||
{file = "coverage-7.2.5-cp37-cp37m-win_amd64.whl", hash = "sha256:12580845917b1e59f8a1c2ffa6af6d0908cb39220f3019e36c110c943dc875b0"},
|
||||
{file = "coverage-7.2.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b5016e331b75310610c2cf955d9f58a9749943ed5f7b8cfc0bb89c6134ab0a84"},
|
||||
{file = "coverage-7.2.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:373ea34dca98f2fdb3e5cb33d83b6d801007a8074f992b80311fc589d3e6b790"},
|
||||
{file = "coverage-7.2.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a063aad9f7b4c9f9da7b2550eae0a582ffc7623dca1c925e50c3fbde7a579771"},
|
||||
{file = "coverage-7.2.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38c0a497a000d50491055805313ed83ddba069353d102ece8aef5d11b5faf045"},
|
||||
{file = "coverage-7.2.5-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2b3b05e22a77bb0ae1a3125126a4e08535961c946b62f30985535ed40e26614"},
|
||||
{file = "coverage-7.2.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0342a28617e63ad15d96dca0f7ae9479a37b7d8a295f749c14f3436ea59fdcb3"},
|
||||
{file = "coverage-7.2.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cf97ed82ca986e5c637ea286ba2793c85325b30f869bf64d3009ccc1a31ae3fd"},
|
||||
{file = "coverage-7.2.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c2c41c1b1866b670573657d584de413df701f482574bad7e28214a2362cb1fd1"},
|
||||
{file = "coverage-7.2.5-cp38-cp38-win32.whl", hash = "sha256:10b15394c13544fce02382360cab54e51a9e0fd1bd61ae9ce012c0d1e103c813"},
|
||||
{file = "coverage-7.2.5-cp38-cp38-win_amd64.whl", hash = "sha256:a0b273fe6dc655b110e8dc89b8ec7f1a778d78c9fd9b4bda7c384c8906072212"},
|
||||
{file = "coverage-7.2.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5c587f52c81211d4530fa6857884d37f514bcf9453bdeee0ff93eaaf906a5c1b"},
|
||||
{file = "coverage-7.2.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4436cc9ba5414c2c998eaedee5343f49c02ca93b21769c5fdfa4f9d799e84200"},
|
||||
{file = "coverage-7.2.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6599bf92f33ab041e36e06d25890afbdf12078aacfe1f1d08c713906e49a3fe5"},
|
||||
{file = "coverage-7.2.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:857abe2fa6a4973f8663e039ead8d22215d31db613ace76e4a98f52ec919068e"},
|
||||
{file = "coverage-7.2.5-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6f5cab2d7f0c12f8187a376cc6582c477d2df91d63f75341307fcdcb5d60303"},
|
||||
{file = "coverage-7.2.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:aa387bd7489f3e1787ff82068b295bcaafbf6f79c3dad3cbc82ef88ce3f48ad3"},
|
||||
{file = "coverage-7.2.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:156192e5fd3dbbcb11cd777cc469cf010a294f4c736a2b2c891c77618cb1379a"},
|
||||
{file = "coverage-7.2.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bd3b4b8175c1db502adf209d06136c000df4d245105c8839e9d0be71c94aefe1"},
|
||||
{file = "coverage-7.2.5-cp39-cp39-win32.whl", hash = "sha256:ddc5a54edb653e9e215f75de377354e2455376f416c4378e1d43b08ec50acc31"},
|
||||
{file = "coverage-7.2.5-cp39-cp39-win_amd64.whl", hash = "sha256:338aa9d9883aaaad53695cb14ccdeb36d4060485bb9388446330bef9c361c252"},
|
||||
{file = "coverage-7.2.5-pp37.pp38.pp39-none-any.whl", hash = "sha256:8877d9b437b35a85c18e3c6499b23674684bf690f5d96c1006a1ef61f9fdf0f3"},
|
||||
{file = "coverage-7.2.5.tar.gz", hash = "sha256:f99ef080288f09ffc687423b8d60978cf3a465d3f404a18d1a05474bd8575a47"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""}
|
||||
|
||||
[package.extras]
|
||||
toml = ["tomli"]
|
||||
|
||||
[[package]]
|
||||
name = "exceptiongroup"
|
||||
version = "1.1.1"
|
||||
description = "Backport of PEP 654 (exception groups)"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "exceptiongroup-1.1.1-py3-none-any.whl", hash = "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e"},
|
||||
{file = "exceptiongroup-1.1.1.tar.gz", hash = "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
test = ["pytest (>=6)"]
|
||||
|
||||
[[package]]
|
||||
name = "flake8"
|
||||
version = "5.0.4"
|
||||
description = "the modular source code checker: pep8 pyflakes and co"
|
||||
optional = false
|
||||
python-versions = ">=3.6.1"
|
||||
files = [
|
||||
{file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"},
|
||||
{file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
mccabe = ">=0.7.0,<0.8.0"
|
||||
pycodestyle = ">=2.9.0,<2.10.0"
|
||||
pyflakes = ">=2.5.0,<2.6.0"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.4"
|
||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
{file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
|
||||
{file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
version = "2.0.0"
|
||||
description = "brain-dead simple config-ini parsing"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
|
||||
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "isort"
|
||||
version = "5.12.0"
|
||||
description = "A Python utility / library to sort Python imports."
|
||||
optional = false
|
||||
python-versions = ">=3.8.0"
|
||||
files = [
|
||||
{file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"},
|
||||
{file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
colors = ["colorama (>=0.4.3)"]
|
||||
pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"]
|
||||
plugins = ["setuptools"]
|
||||
requirements-deprecated-finder = ["pip-api", "pipreqs"]
|
||||
|
||||
[[package]]
|
||||
name = "mccabe"
|
||||
version = "0.7.0"
|
||||
description = "McCabe checker, plugin for flake8"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
|
||||
{file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mypy"
|
||||
version = "0.991"
|
||||
description = "Optional static typing for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "mypy-0.991-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7d17e0a9707d0772f4a7b878f04b4fd11f6f5bcb9b3813975a9b13c9332153ab"},
|
||||
{file = "mypy-0.991-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0714258640194d75677e86c786e80ccf294972cc76885d3ebbb560f11db0003d"},
|
||||
{file = "mypy-0.991-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c8f3be99e8a8bd403caa8c03be619544bc2c77a7093685dcf308c6b109426c6"},
|
||||
{file = "mypy-0.991-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc9ec663ed6c8f15f4ae9d3c04c989b744436c16d26580eaa760ae9dd5d662eb"},
|
||||
{file = "mypy-0.991-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4307270436fd7694b41f913eb09210faff27ea4979ecbcd849e57d2da2f65305"},
|
||||
{file = "mypy-0.991-cp310-cp310-win_amd64.whl", hash = "sha256:901c2c269c616e6cb0998b33d4adbb4a6af0ac4ce5cd078afd7bc95830e62c1c"},
|
||||
{file = "mypy-0.991-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d13674f3fb73805ba0c45eb6c0c3053d218aa1f7abead6e446d474529aafc372"},
|
||||
{file = "mypy-0.991-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1c8cd4fb70e8584ca1ed5805cbc7c017a3d1a29fb450621089ffed3e99d1857f"},
|
||||
{file = "mypy-0.991-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:209ee89fbb0deed518605edddd234af80506aec932ad28d73c08f1400ef80a33"},
|
||||
{file = "mypy-0.991-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37bd02ebf9d10e05b00d71302d2c2e6ca333e6c2a8584a98c00e038db8121f05"},
|
||||
{file = "mypy-0.991-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:26efb2fcc6b67e4d5a55561f39176821d2adf88f2745ddc72751b7890f3194ad"},
|
||||
{file = "mypy-0.991-cp311-cp311-win_amd64.whl", hash = "sha256:3a700330b567114b673cf8ee7388e949f843b356a73b5ab22dd7cff4742a5297"},
|
||||
{file = "mypy-0.991-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1f7d1a520373e2272b10796c3ff721ea1a0712288cafaa95931e66aa15798813"},
|
||||
{file = "mypy-0.991-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:641411733b127c3e0dab94c45af15fea99e4468f99ac88b39efb1ad677da5711"},
|
||||
{file = "mypy-0.991-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3d80e36b7d7a9259b740be6d8d906221789b0d836201af4234093cae89ced0cd"},
|
||||
{file = "mypy-0.991-cp37-cp37m-win_amd64.whl", hash = "sha256:e62ebaad93be3ad1a828a11e90f0e76f15449371ffeecca4a0a0b9adc99abcef"},
|
||||
{file = "mypy-0.991-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b86ce2c1866a748c0f6faca5232059f881cda6dda2a893b9a8373353cfe3715a"},
|
||||
{file = "mypy-0.991-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac6e503823143464538efda0e8e356d871557ef60ccd38f8824a4257acc18d93"},
|
||||
{file = "mypy-0.991-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0cca5adf694af539aeaa6ac633a7afe9bbd760df9d31be55ab780b77ab5ae8bf"},
|
||||
{file = "mypy-0.991-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a12c56bf73cdab116df96e4ff39610b92a348cc99a1307e1da3c3768bbb5b135"},
|
||||
{file = "mypy-0.991-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:652b651d42f155033a1967739788c436491b577b6a44e4c39fb340d0ee7f0d70"},
|
||||
{file = "mypy-0.991-cp38-cp38-win_amd64.whl", hash = "sha256:4175593dc25d9da12f7de8de873a33f9b2b8bdb4e827a7cae952e5b1a342e243"},
|
||||
{file = "mypy-0.991-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:98e781cd35c0acf33eb0295e8b9c55cdbef64fcb35f6d3aa2186f289bed6e80d"},
|
||||
{file = "mypy-0.991-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6d7464bac72a85cb3491c7e92b5b62f3dcccb8af26826257760a552a5e244aa5"},
|
||||
{file = "mypy-0.991-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c9166b3f81a10cdf9b49f2d594b21b31adadb3d5e9db9b834866c3258b695be3"},
|
||||
{file = "mypy-0.991-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8472f736a5bfb159a5e36740847808f6f5b659960115ff29c7cecec1741c648"},
|
||||
{file = "mypy-0.991-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e80e758243b97b618cdf22004beb09e8a2de1af481382e4d84bc52152d1c476"},
|
||||
{file = "mypy-0.991-cp39-cp39-win_amd64.whl", hash = "sha256:74e259b5c19f70d35fcc1ad3d56499065c601dfe94ff67ae48b85596b9ec1461"},
|
||||
{file = "mypy-0.991-py3-none-any.whl", hash = "sha256:de32edc9b0a7e67c2775e574cb061a537660e51210fbf6006b0b36ea695ae9bb"},
|
||||
{file = "mypy-0.991.tar.gz", hash = "sha256:3c0165ba8f354a6d9881809ef29f1a9318a236a6d81c690094c5df32107bde06"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
mypy-extensions = ">=0.4.3"
|
||||
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
|
||||
typing-extensions = ">=3.10"
|
||||
|
||||
[package.extras]
|
||||
dmypy = ["psutil (>=4.0)"]
|
||||
install-types = ["pip"]
|
||||
python2 = ["typed-ast (>=1.4.0,<2)"]
|
||||
reports = ["lxml"]
|
||||
|
||||
[[package]]
|
||||
name = "mypy-extensions"
|
||||
version = "1.0.0"
|
||||
description = "Type system extensions for programs checked with the mypy type checker."
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
{file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
|
||||
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "numpy"
|
||||
version = "1.24.3"
|
||||
description = "Fundamental package for array computing in Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "numpy-1.24.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3c1104d3c036fb81ab923f507536daedc718d0ad5a8707c6061cdfd6d184e570"},
|
||||
{file = "numpy-1.24.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:202de8f38fc4a45a3eea4b63e2f376e5f2dc64ef0fa692838e31a808520efaf7"},
|
||||
{file = "numpy-1.24.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8535303847b89aa6b0f00aa1dc62867b5a32923e4d1681a35b5eef2d9591a463"},
|
||||
{file = "numpy-1.24.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d926b52ba1367f9acb76b0df6ed21f0b16a1ad87c6720a1121674e5cf63e2b6"},
|
||||
{file = "numpy-1.24.3-cp310-cp310-win32.whl", hash = "sha256:f21c442fdd2805e91799fbe044a7b999b8571bb0ab0f7850d0cb9641a687092b"},
|
||||
{file = "numpy-1.24.3-cp310-cp310-win_amd64.whl", hash = "sha256:ab5f23af8c16022663a652d3b25dcdc272ac3f83c3af4c02eb8b824e6b3ab9d7"},
|
||||
{file = "numpy-1.24.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9a7721ec204d3a237225db3e194c25268faf92e19338a35f3a224469cb6039a3"},
|
||||
{file = "numpy-1.24.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d6cc757de514c00b24ae8cf5c876af2a7c3df189028d68c0cb4eaa9cd5afc2bf"},
|
||||
{file = "numpy-1.24.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76e3f4e85fc5d4fd311f6e9b794d0c00e7002ec122be271f2019d63376f1d385"},
|
||||
{file = "numpy-1.24.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1d3c026f57ceaad42f8231305d4653d5f05dc6332a730ae5c0bea3513de0950"},
|
||||
{file = "numpy-1.24.3-cp311-cp311-win32.whl", hash = "sha256:c91c4afd8abc3908e00a44b2672718905b8611503f7ff87390cc0ac3423fb096"},
|
||||
{file = "numpy-1.24.3-cp311-cp311-win_amd64.whl", hash = "sha256:5342cf6aad47943286afa6f1609cad9b4266a05e7f2ec408e2cf7aea7ff69d80"},
|
||||
{file = "numpy-1.24.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7776ea65423ca6a15255ba1872d82d207bd1e09f6d0894ee4a64678dd2204078"},
|
||||
{file = "numpy-1.24.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ae8d0be48d1b6ed82588934aaaa179875e7dc4f3d84da18d7eae6eb3f06c242c"},
|
||||
{file = "numpy-1.24.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecde0f8adef7dfdec993fd54b0f78183051b6580f606111a6d789cd14c61ea0c"},
|
||||
{file = "numpy-1.24.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4749e053a29364d3452c034827102ee100986903263e89884922ef01a0a6fd2f"},
|
||||
{file = "numpy-1.24.3-cp38-cp38-win32.whl", hash = "sha256:d933fabd8f6a319e8530d0de4fcc2e6a61917e0b0c271fded460032db42a0fe4"},
|
||||
{file = "numpy-1.24.3-cp38-cp38-win_amd64.whl", hash = "sha256:56e48aec79ae238f6e4395886b5eaed058abb7231fb3361ddd7bfdf4eed54289"},
|
||||
{file = "numpy-1.24.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4719d5aefb5189f50887773699eaf94e7d1e02bf36c1a9d353d9f46703758ca4"},
|
||||
{file = "numpy-1.24.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ec87a7084caa559c36e0a2309e4ecb1baa03b687201d0a847c8b0ed476a7187"},
|
||||
{file = "numpy-1.24.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea8282b9bcfe2b5e7d491d0bf7f3e2da29700cec05b49e64d6246923329f2b02"},
|
||||
{file = "numpy-1.24.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210461d87fb02a84ef243cac5e814aad2b7f4be953b32cb53327bb49fd77fbb4"},
|
||||
{file = "numpy-1.24.3-cp39-cp39-win32.whl", hash = "sha256:784c6da1a07818491b0ffd63c6bbe5a33deaa0e25a20e1b3ea20cf0e43f8046c"},
|
||||
{file = "numpy-1.24.3-cp39-cp39-win_amd64.whl", hash = "sha256:d5036197ecae68d7f491fcdb4df90082b0d4960ca6599ba2659957aafced7c17"},
|
||||
{file = "numpy-1.24.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:352ee00c7f8387b44d19f4cada524586f07379c0d49270f87233983bc5087ca0"},
|
||||
{file = "numpy-1.24.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7d6acc2e7524c9955e5c903160aa4ea083736fde7e91276b0e5d98e6332812"},
|
||||
{file = "numpy-1.24.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:35400e6a8d102fd07c71ed7dcadd9eb62ee9a6e84ec159bd48c28235bbb0f8e4"},
|
||||
{file = "numpy-1.24.3.tar.gz", hash = "sha256:ab344f1bf21f140adab8e47fdbc7c35a477dc01408791f8ba00d018dd0bc5155"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "23.1"
|
||||
description = "Core utilities for Python packages"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"},
|
||||
{file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pathspec"
|
||||
version = "0.11.1"
|
||||
description = "Utility library for gitignore style pattern matching of file paths."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"},
|
||||
{file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "3.5.1"
|
||||
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "platformdirs-3.5.1-py3-none-any.whl", hash = "sha256:e2378146f1964972c03c085bb5662ae80b2b8c06226c54b2ff4aa9483e8a13a5"},
|
||||
{file = "platformdirs-3.5.1.tar.gz", hash = "sha256:412dae91f52a6f84830f39a8078cecd0e866cb72294a5c66808e74d5e88d251f"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo (>=2023.3.27)", "proselint (>=0.13)", "sphinx (>=6.2.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"]
|
||||
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.0.0"
|
||||
description = "plugin and hook calling mechanisms for python"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
|
||||
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dev = ["pre-commit", "tox"]
|
||||
testing = ["pytest", "pytest-benchmark"]
|
||||
|
||||
[[package]]
|
||||
name = "py"
|
||||
version = "1.11.0"
|
||||
description = "library with cross-python path, ini-parsing, io, code, log facilities"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
files = [
|
||||
{file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
|
||||
{file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pycodestyle"
|
||||
version = "2.9.1"
|
||||
description = "Python style guide checker"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"},
|
||||
{file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pycparser"
|
||||
version = "2.21"
|
||||
description = "C parser in Python"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
files = [
|
||||
{file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
|
||||
{file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyflakes"
|
||||
version = "2.5.0"
|
||||
description = "passive checker of Python programs"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"},
|
||||
{file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyrtlsdr"
|
||||
version = "0.2.93"
|
||||
description = "A Python wrapper for librtlsdr (a driver for Realtek RTL2832U based SDR's)"
|
||||
optional = true
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "pyrtlsdr-0.2.93-py2.py3-none-any.whl", hash = "sha256:9a35999876092af963869b499e6094555626c0c3bab45308643f9411495e8cc7"},
|
||||
{file = "pyrtlsdr-0.2.93.tar.gz", hash = "sha256:2de29bb4e4037088a78e89012bc2cc84b73dc45c60608b28b7d903f62923ba26"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "7.3.1"
|
||||
description = "pytest: simple powerful testing with Python"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "pytest-7.3.1-py3-none-any.whl", hash = "sha256:3799fa815351fea3a5e96ac7e503a96fa51cc9942c3753cda7651b93c1cfa362"},
|
||||
{file = "pytest-7.3.1.tar.gz", hash = "sha256:434afafd78b1d78ed0addf160ad2b77a30d35d4bdf8af234fe621919d9ed15e3"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
|
||||
iniconfig = "*"
|
||||
packaging = "*"
|
||||
pluggy = ">=0.12,<2.0"
|
||||
tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
|
||||
|
||||
[package.extras]
|
||||
testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-cov"
|
||||
version = "4.0.0"
|
||||
description = "Pytest plugin for measuring coverage."
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"},
|
||||
{file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
coverage = {version = ">=5.2.1", extras = ["toml"]}
|
||||
pytest = ">=4.6"
|
||||
|
||||
[package.extras]
|
||||
testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"]
|
||||
|
||||
[[package]]
|
||||
name = "pyzmq"
|
||||
version = "24.0.1"
|
||||
description = "Python bindings for 0MQ"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "pyzmq-24.0.1-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:28b119ba97129d3001673a697b7cce47fe6de1f7255d104c2f01108a5179a066"},
|
||||
{file = "pyzmq-24.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bcbebd369493d68162cddb74a9c1fcebd139dfbb7ddb23d8f8e43e6c87bac3a6"},
|
||||
{file = "pyzmq-24.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae61446166983c663cee42c852ed63899e43e484abf080089f771df4b9d272ef"},
|
||||
{file = "pyzmq-24.0.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87f7ac99b15270db8d53f28c3c7b968612993a90a5cf359da354efe96f5372b4"},
|
||||
{file = "pyzmq-24.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9dca7c3956b03b7663fac4d150f5e6d4f6f38b2462c1e9afd83bcf7019f17913"},
|
||||
{file = "pyzmq-24.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8c78bfe20d4c890cb5580a3b9290f700c570e167d4cdcc55feec07030297a5e3"},
|
||||
{file = "pyzmq-24.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:48f721f070726cd2a6e44f3c33f8ee4b24188e4b816e6dd8ba542c8c3bb5b246"},
|
||||
{file = "pyzmq-24.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:afe1f3bc486d0ce40abb0a0c9adb39aed3bbac36ebdc596487b0cceba55c21c1"},
|
||||
{file = "pyzmq-24.0.1-cp310-cp310-win32.whl", hash = "sha256:3e6192dbcefaaa52ed81be88525a54a445f4b4fe2fffcae7fe40ebb58bd06bfd"},
|
||||
{file = "pyzmq-24.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:86de64468cad9c6d269f32a6390e210ca5ada568c7a55de8e681ca3b897bb340"},
|
||||
{file = "pyzmq-24.0.1-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:838812c65ed5f7c2bd11f7b098d2e5d01685a3f6d1f82849423b570bae698c00"},
|
||||
{file = "pyzmq-24.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dfb992dbcd88d8254471760879d48fb20836d91baa90f181c957122f9592b3dc"},
|
||||
{file = "pyzmq-24.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7abddb2bd5489d30ffeb4b93a428130886c171b4d355ccd226e83254fcb6b9ef"},
|
||||
{file = "pyzmq-24.0.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94010bd61bc168c103a5b3b0f56ed3b616688192db7cd5b1d626e49f28ff51b3"},
|
||||
{file = "pyzmq-24.0.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8242543c522d84d033fe79be04cb559b80d7eb98ad81b137ff7e0a9020f00ace"},
|
||||
{file = "pyzmq-24.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ccb94342d13e3bf3ffa6e62f95b5e3f0bc6bfa94558cb37f4b3d09d6feb536ff"},
|
||||
{file = "pyzmq-24.0.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6640f83df0ae4ae1104d4c62b77e9ef39be85ebe53f636388707d532bee2b7b8"},
|
||||
{file = "pyzmq-24.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a180dbd5ea5d47c2d3b716d5c19cc3fb162d1c8db93b21a1295d69585bfddac1"},
|
||||
{file = "pyzmq-24.0.1-cp311-cp311-win32.whl", hash = "sha256:624321120f7e60336be8ec74a172ae7fba5c3ed5bf787cc85f7e9986c9e0ebc2"},
|
||||
{file = "pyzmq-24.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:1724117bae69e091309ffb8255412c4651d3f6355560d9af312d547f6c5bc8b8"},
|
||||
{file = "pyzmq-24.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:15975747462ec49fdc863af906bab87c43b2491403ab37a6d88410635786b0f4"},
|
||||
{file = "pyzmq-24.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b947e264f0e77d30dcbccbb00f49f900b204b922eb0c3a9f0afd61aaa1cedc3d"},
|
||||
{file = "pyzmq-24.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0ec91f1bad66f3ee8c6deb65fa1fe418e8ad803efedd69c35f3b5502f43bd1dc"},
|
||||
{file = "pyzmq-24.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:db03704b3506455d86ec72c3358a779e9b1d07b61220dfb43702b7b668edcd0d"},
|
||||
{file = "pyzmq-24.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:e7e66b4e403c2836ac74f26c4b65d8ac0ca1eef41dfcac2d013b7482befaad83"},
|
||||
{file = "pyzmq-24.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:7a23ccc1083c260fa9685c93e3b170baba45aeed4b524deb3f426b0c40c11639"},
|
||||
{file = "pyzmq-24.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:fa0ae3275ef706c0309556061185dd0e4c4cd3b7d6f67ae617e4e677c7a41e2e"},
|
||||
{file = "pyzmq-24.0.1-cp36-cp36m-win32.whl", hash = "sha256:f01de4ec083daebf210531e2cca3bdb1608dbbbe00a9723e261d92087a1f6ebc"},
|
||||
{file = "pyzmq-24.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:de4217b9eb8b541cf2b7fde4401ce9d9a411cc0af85d410f9d6f4333f43640be"},
|
||||
{file = "pyzmq-24.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:78068e8678ca023594e4a0ab558905c1033b2d3e806a0ad9e3094e231e115a33"},
|
||||
{file = "pyzmq-24.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77c2713faf25a953c69cf0f723d1b7dd83827b0834e6c41e3fb3bbc6765914a1"},
|
||||
{file = "pyzmq-24.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8bb4af15f305056e95ca1bd086239b9ebc6ad55e9f49076d27d80027f72752f6"},
|
||||
{file = "pyzmq-24.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:0f14cffd32e9c4c73da66db97853a6aeceaac34acdc0fae9e5bbc9370281864c"},
|
||||
{file = "pyzmq-24.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0108358dab8c6b27ff6b985c2af4b12665c1bc659648284153ee501000f5c107"},
|
||||
{file = "pyzmq-24.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:d66689e840e75221b0b290b0befa86f059fb35e1ee6443bce51516d4d61b6b99"},
|
||||
{file = "pyzmq-24.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ae08ac90aa8fa14caafc7a6251bd218bf6dac518b7bff09caaa5e781119ba3f2"},
|
||||
{file = "pyzmq-24.0.1-cp37-cp37m-win32.whl", hash = "sha256:8421aa8c9b45ea608c205db9e1c0c855c7e54d0e9c2c2f337ce024f6843cab3b"},
|
||||
{file = "pyzmq-24.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:54d8b9c5e288362ec8595c1d98666d36f2070fd0c2f76e2b3c60fbad9bd76227"},
|
||||
{file = "pyzmq-24.0.1-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:acbd0a6d61cc954b9f535daaa9ec26b0a60a0d4353c5f7c1438ebc88a359a47e"},
|
||||
{file = "pyzmq-24.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:47b11a729d61a47df56346283a4a800fa379ae6a85870d5a2e1e4956c828eedc"},
|
||||
{file = "pyzmq-24.0.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:abe6eb10122f0d746a0d510c2039ae8edb27bc9af29f6d1b05a66cc2401353ff"},
|
||||
{file = "pyzmq-24.0.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:07bec1a1b22dacf718f2c0e71b49600bb6a31a88f06527dfd0b5aababe3fa3f7"},
|
||||
{file = "pyzmq-24.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0d945a85b70da97ae86113faf9f1b9294efe66bd4a5d6f82f2676d567338b66"},
|
||||
{file = "pyzmq-24.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1b7928bb7580736ffac5baf814097be342ba08d3cfdfb48e52773ec959572287"},
|
||||
{file = "pyzmq-24.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b946da90dc2799bcafa682692c1d2139b2a96ec3c24fa9fc6f5b0da782675330"},
|
||||
{file = "pyzmq-24.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c8840f064b1fb377cffd3efeaad2b190c14d4c8da02316dae07571252d20b31f"},
|
||||
{file = "pyzmq-24.0.1-cp38-cp38-win32.whl", hash = "sha256:4854f9edc5208f63f0841c0c667260ae8d6846cfa233c479e29fdc85d42ebd58"},
|
||||
{file = "pyzmq-24.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:42d4f97b9795a7aafa152a36fe2ad44549b83a743fd3e77011136def512e6c2a"},
|
||||
{file = "pyzmq-24.0.1-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:52afb0ac962963fff30cf1be775bc51ae083ef4c1e354266ab20e5382057dd62"},
|
||||
{file = "pyzmq-24.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8bad8210ad4df68c44ff3685cca3cda448ee46e20d13edcff8909eba6ec01ca4"},
|
||||
{file = "pyzmq-24.0.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:dabf1a05318d95b1537fd61d9330ef4313ea1216eea128a17615038859da3b3b"},
|
||||
{file = "pyzmq-24.0.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5bd3d7dfd9cd058eb68d9a905dec854f86649f64d4ddf21f3ec289341386c44b"},
|
||||
{file = "pyzmq-24.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8012bce6836d3f20a6c9599f81dfa945f433dab4dbd0c4917a6fb1f998ab33d"},
|
||||
{file = "pyzmq-24.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c31805d2c8ade9b11feca4674eee2b9cce1fec3e8ddb7bbdd961a09dc76a80ea"},
|
||||
{file = "pyzmq-24.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:3104f4b084ad5d9c0cb87445cc8cfd96bba710bef4a66c2674910127044df209"},
|
||||
{file = "pyzmq-24.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:df0841f94928f8af9c7a1f0aaaffba1fb74607af023a152f59379c01c53aee58"},
|
||||
{file = "pyzmq-24.0.1-cp39-cp39-win32.whl", hash = "sha256:a435ef8a3bd95c8a2d316d6e0ff70d0db524f6037411652803e118871d703333"},
|
||||
{file = "pyzmq-24.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:2032d9cb994ce3b4cba2b8dfae08c7e25bc14ba484c770d4d3be33c27de8c45b"},
|
||||
{file = "pyzmq-24.0.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bb5635c851eef3a7a54becde6da99485eecf7d068bd885ac8e6d173c4ecd68b0"},
|
||||
{file = "pyzmq-24.0.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:83ea1a398f192957cb986d9206ce229efe0ee75e3c6635baff53ddf39bd718d5"},
|
||||
{file = "pyzmq-24.0.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:941fab0073f0a54dc33d1a0460cb04e0d85893cb0c5e1476c785000f8b359409"},
|
||||
{file = "pyzmq-24.0.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e8f482c44ccb5884bf3f638f29bea0f8dc68c97e38b2061769c4cb697f6140d"},
|
||||
{file = "pyzmq-24.0.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:613010b5d17906c4367609e6f52e9a2595e35d5cc27d36ff3f1b6fa6e954d944"},
|
||||
{file = "pyzmq-24.0.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:65c94410b5a8355cfcf12fd600a313efee46ce96a09e911ea92cf2acf6708804"},
|
||||
{file = "pyzmq-24.0.1-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:20e7eeb1166087db636c06cae04a1ef59298627f56fb17da10528ab52a14c87f"},
|
||||
{file = "pyzmq-24.0.1-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a2712aee7b3834ace51738c15d9ee152cc5a98dc7d57dd93300461b792ab7b43"},
|
||||
{file = "pyzmq-24.0.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a7c280185c4da99e0cc06c63bdf91f5b0b71deb70d8717f0ab870a43e376db8"},
|
||||
{file = "pyzmq-24.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:858375573c9225cc8e5b49bfac846a77b696b8d5e815711b8d4ba3141e6e8879"},
|
||||
{file = "pyzmq-24.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:80093b595921eed1a2cead546a683b9e2ae7f4a4592bb2ab22f70d30174f003a"},
|
||||
{file = "pyzmq-24.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f3f3154fde2b1ff3aa7b4f9326347ebc89c8ef425ca1db8f665175e6d3bd42f"},
|
||||
{file = "pyzmq-24.0.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abb756147314430bee5d10919b8493c0ccb109ddb7f5dfd2fcd7441266a25b75"},
|
||||
{file = "pyzmq-24.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44e706bac34e9f50779cb8c39f10b53a4d15aebb97235643d3112ac20bd577b4"},
|
||||
{file = "pyzmq-24.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:687700f8371643916a1d2c61f3fdaa630407dd205c38afff936545d7b7466066"},
|
||||
{file = "pyzmq-24.0.1.tar.gz", hash = "sha256:216f5d7dbb67166759e59b0479bca82b8acf9bed6015b526b8eb10143fb08e77"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
cffi = {version = "*", markers = "implementation_name == \"pypy\""}
|
||||
py = {version = "*", markers = "implementation_name == \"pypy\""}
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.30.0"
|
||||
description = "Python HTTP for Humans."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "requests-2.30.0-py3-none-any.whl", hash = "sha256:10e94cc4f3121ee6da529d358cdaeaff2f1c409cd377dbc72b825852f2f7e294"},
|
||||
{file = "requests-2.30.0.tar.gz", hash = "sha256:239d7d4458afcb28a692cdd298d87542235f4ca8d36d03a15bfc128a6559a2f4"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
certifi = ">=2017.4.17"
|
||||
charset-normalizer = ">=2,<4"
|
||||
idna = ">=2.5,<4"
|
||||
urllib3 = ">=1.21.1,<3"
|
||||
|
||||
[package.extras]
|
||||
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
|
||||
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
|
||||
|
||||
[[package]]
|
||||
name = "tomli"
|
||||
version = "2.0.1"
|
||||
description = "A lil' TOML parser"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
|
||||
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.5.0"
|
||||
description = "Backported and Experimental Type Hints for Python 3.7+"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"},
|
||||
{file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.0.2"
|
||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "urllib3-2.0.2-py3-none-any.whl", hash = "sha256:d055c2f9d38dc53c808f6fdc8eab7360b6fdbbde02340ed25cfbcd817c62469e"},
|
||||
{file = "urllib3-2.0.2.tar.gz", hash = "sha256:61717a1095d7e155cdb737ac7bb2f4324a858a1e2e6466f6d03ff630ca68d3cc"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
|
||||
secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"]
|
||||
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||
zstd = ["zstandard (>=0.18.0)"]
|
||||
|
||||
[extras]
|
||||
rtlsdr = ["pyrtlsdr"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.8"
|
||||
content-hash = "53dde5ef0e7bc3d687076c6b3e535101dce9db8877e196bf8fe81bb2773c0f17"
|
||||
3
pyModeS/.gitignore
vendored
Normal file
3
pyModeS/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
decoder/flarm/decode.c
|
||||
extra/demod2400/core.c
|
||||
c_common.c
|
||||
@@ -1,17 +1,35 @@
|
||||
from __future__ import absolute_import, print_function, division
|
||||
|
||||
import os
|
||||
import warnings
|
||||
|
||||
from .decoder.common import *
|
||||
try:
|
||||
from . import c_common as common
|
||||
from .c_common import *
|
||||
except Exception:
|
||||
from . import py_common as common # type: ignore
|
||||
from .py_common import * # type: ignore
|
||||
|
||||
from .decoder import tell
|
||||
from .decoder import adsb
|
||||
from .decoder import commb
|
||||
from .decoder import common
|
||||
from .decoder import allcall
|
||||
from .decoder import surv
|
||||
from .decoder import bds
|
||||
from .extra import aero
|
||||
from .extra import tcpclient
|
||||
|
||||
__all__ = [
|
||||
"common",
|
||||
"tell",
|
||||
"adsb",
|
||||
"commb",
|
||||
"allcall",
|
||||
"surv",
|
||||
"bds",
|
||||
"aero",
|
||||
"tcpclient",
|
||||
]
|
||||
|
||||
|
||||
warnings.simplefilter("once", DeprecationWarning)
|
||||
|
||||
dirpath = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
28
pyModeS/c_common.pxd
Normal file
28
pyModeS/c_common.pxd
Normal file
@@ -0,0 +1,28 @@
|
||||
# cython: language_level=3
|
||||
|
||||
cdef int char_to_int(unsigned char binstr)
|
||||
cdef unsigned char int_to_char(unsigned char i)
|
||||
|
||||
cpdef str hex2bin(str hexstr)
|
||||
cpdef long bin2int(str binstr)
|
||||
cpdef long hex2int(str hexstr)
|
||||
cpdef str bin2hex(str binstr)
|
||||
|
||||
cpdef unsigned char df(str msg)
|
||||
cpdef long crc(str msg, bint encode=*)
|
||||
|
||||
cpdef long floor(double x)
|
||||
cpdef str icao(str msg)
|
||||
cpdef bint is_icao_assigned(str icao)
|
||||
|
||||
cpdef int typecode(str msg)
|
||||
cpdef int cprNL(double lat)
|
||||
|
||||
cpdef str idcode(str msg)
|
||||
cpdef str squawk(str binstr)
|
||||
|
||||
cpdef int altcode(str msg)
|
||||
cpdef int altitude(str binstr)
|
||||
|
||||
cpdef str data(str msg)
|
||||
cpdef bint allzeros(str msg)
|
||||
18
pyModeS/c_common.pyi
Normal file
18
pyModeS/c_common.pyi
Normal file
@@ -0,0 +1,18 @@
|
||||
def hex2bin(hexstr: str) -> str: ...
|
||||
def bin2int(binstr: str) -> int: ...
|
||||
def hex2int(hexstr: str) -> int: ...
|
||||
def bin2hex(binstr: str) -> str: ...
|
||||
def df(msg: str) -> int: ...
|
||||
def crc(msg: str, encode: bool = False) -> int: ...
|
||||
def floor(x: float) -> float: ...
|
||||
def icao(msg: str) -> str: ...
|
||||
def is_icao_assigned(icao: str) -> bool: ...
|
||||
def typecode(msg: str) -> int: ...
|
||||
def cprNL(lat: float) -> int: ...
|
||||
def idcode(msg: str) -> str: ...
|
||||
def squawk(binstr: str) -> str: ...
|
||||
def altcode(msg: str) -> int: ...
|
||||
def altitude(binstr: str) -> int: ...
|
||||
def data(msg: str) -> str: ...
|
||||
def allzeros(msg: str) -> bool: ...
|
||||
def wrongstatus(data: str, sb: int, msb: int, lsb: int) -> bool: ...
|
||||
405
pyModeS/c_common.pyx
Normal file
405
pyModeS/c_common.pyx
Normal file
@@ -0,0 +1,405 @@
|
||||
# cython: language_level=3
|
||||
|
||||
cimport cython
|
||||
from cpython cimport array
|
||||
from cpython.bytes cimport PyBytes_GET_SIZE
|
||||
from cpython.bytearray cimport PyByteArray_GET_SIZE
|
||||
|
||||
from libc.math cimport abs, cos, acos, fabs, M_PI as pi, floor as c_floor
|
||||
|
||||
|
||||
cdef int char_to_int(unsigned char binstr):
|
||||
if 48 <= binstr <= 57: # 0 to 9
|
||||
return binstr - 48
|
||||
if 97 <= binstr <= 102: # a to f
|
||||
return binstr - 97 + 10
|
||||
if 65 <= binstr <= 70: # A to F
|
||||
return binstr - 65 + 10
|
||||
return 0
|
||||
|
||||
cdef unsigned char int_to_char(unsigned char i):
|
||||
if i < 10:
|
||||
return 48 + i # "0" + i
|
||||
return 97 - 10 + i # "a" - 10 + i
|
||||
|
||||
@cython.boundscheck(False)
|
||||
@cython.overflowcheck(False)
|
||||
cpdef str hex2bin(str hexstr):
|
||||
"""Convert a hexadecimal string to binary string, with zero fillings."""
|
||||
# num_of_bits = len(hexstr) * 4
|
||||
cdef hexbytes = bytes(hexstr.encode())
|
||||
cdef Py_ssize_t len_hexstr = PyBytes_GET_SIZE(hexbytes)
|
||||
# binstr = bin(int(hexbytes, 16))[2:].zfill(int(num_of_bits))
|
||||
cdef bytearray _binstr = bytearray(4 * len_hexstr)
|
||||
cdef unsigned char[:] binstr = _binstr
|
||||
cdef unsigned char int_
|
||||
cdef Py_ssize_t i
|
||||
for i in range(len_hexstr):
|
||||
int_ = char_to_int(hexbytes[i])
|
||||
binstr[4*i] = int_to_char((int_ >> 3) & 1)
|
||||
binstr[4*i+1] = int_to_char((int_ >> 2) & 1)
|
||||
binstr[4*i+2] = int_to_char((int_ >> 1) & 1)
|
||||
binstr[4*i+3] = int_to_char((int_) & 1)
|
||||
return _binstr.decode()
|
||||
|
||||
@cython.boundscheck(False)
|
||||
cpdef long bin2int(str binstr):
|
||||
"""Convert a binary string to integer."""
|
||||
# return int(binstr, 2)
|
||||
cdef bytearray binbytes = bytearray(binstr.encode())
|
||||
cdef Py_ssize_t len_ = PyByteArray_GET_SIZE(binbytes)
|
||||
cdef long cumul = 0
|
||||
cdef unsigned char[:] v_binstr = binbytes
|
||||
for i in range(len_):
|
||||
cumul = 2*cumul + char_to_int(v_binstr[i])
|
||||
return cumul
|
||||
|
||||
@cython.boundscheck(False)
|
||||
cpdef long hex2int(str hexstr):
|
||||
"""Convert a binary string to integer."""
|
||||
# return int(hexstr, 2)
|
||||
cdef bytearray binbytes = bytearray(hexstr.encode())
|
||||
cdef Py_ssize_t len_ = PyByteArray_GET_SIZE(binbytes)
|
||||
cdef long cumul = 0
|
||||
cdef unsigned char[:] v_hexstr = binbytes
|
||||
for i in range(len_):
|
||||
cumul = 16*cumul + char_to_int(v_hexstr[i])
|
||||
return cumul
|
||||
|
||||
@cython.boundscheck(False)
|
||||
cpdef str bin2hex(str binstr):
|
||||
return "{0:X}".format(int(binstr, 2))
|
||||
|
||||
|
||||
@cython.boundscheck(False)
|
||||
cpdef unsigned char df(str msg):
|
||||
"""Decode Downlink Format value, bits 1 to 5."""
|
||||
cdef str dfbin = hex2bin(msg[:2])
|
||||
# return min(bin2int(dfbin[0:5]), 24)
|
||||
cdef long df = bin2int(dfbin[0:5])
|
||||
if df > 24:
|
||||
return 24
|
||||
return df
|
||||
|
||||
# the CRC generator
|
||||
# G = [int("11111111", 2), int("11111010", 2), int("00000100", 2), int("10000000", 2)]
|
||||
cdef array.array _G = array.array('l', [0b11111111, 0b11111010, 0b00000100, 0b10000000])
|
||||
|
||||
@cython.boundscheck(False)
|
||||
@cython.wraparound(False)
|
||||
cpdef long crc(str msg, bint encode=False):
|
||||
"""Mode-S Cyclic Redundancy Check.
|
||||
|
||||
Detect if bit error occurs in the Mode-S message. When encode option is on,
|
||||
the checksum is generated.
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
encode (bool): True to encode the date only and return the checksum
|
||||
Returns:
|
||||
int: message checksum, or partity bits (encoder)
|
||||
|
||||
"""
|
||||
# the CRC generator
|
||||
# G = [int("11111111", 2), int("11111010", 2), int("00000100", 2), int("10000000", 2)]
|
||||
# cdef array.array _G = array.array('l', [0b11111111, 0b11111010, 0b00000100, 0b10000000])
|
||||
cdef long[4] G = _G
|
||||
|
||||
# msgbin_split = wrap(msgbin, 8)
|
||||
# mbytes = list(map(bin2int, msgbin_split))
|
||||
cdef bytearray _msgbin = bytearray(hex2bin(msg).encode())
|
||||
cdef unsigned char[:] msgbin = _msgbin
|
||||
|
||||
cdef Py_ssize_t len_msgbin = PyByteArray_GET_SIZE(_msgbin)
|
||||
cdef Py_ssize_t len_mbytes = len_msgbin // 8
|
||||
cdef Py_ssize_t i
|
||||
|
||||
if encode:
|
||||
for i in range(len_msgbin - 24, len_msgbin):
|
||||
msgbin[i] = 0
|
||||
|
||||
cdef array.array _mbytes = array.array(
|
||||
'l', [bin2int(_msgbin[8*i:8*i+8].decode()) for i in range(len_mbytes)]
|
||||
)
|
||||
|
||||
cdef long[:] mbytes = _mbytes
|
||||
|
||||
cdef long bits, mask
|
||||
cdef Py_ssize_t ibyte, ibit
|
||||
|
||||
for ibyte in range(len_mbytes - 3):
|
||||
for ibit in range(8):
|
||||
mask = 0x80 >> ibit
|
||||
bits = mbytes[ibyte] & mask
|
||||
|
||||
if bits > 0:
|
||||
mbytes[ibyte] = mbytes[ibyte] ^ (G[0] >> ibit)
|
||||
mbytes[ibyte + 1] = mbytes[ibyte + 1] ^ (
|
||||
0xFF & ((G[0] << 8 - ibit) | (G[1] >> ibit))
|
||||
)
|
||||
mbytes[ibyte + 2] = mbytes[ibyte + 2] ^ (
|
||||
0xFF & ((G[1] << 8 - ibit) | (G[2] >> ibit))
|
||||
)
|
||||
mbytes[ibyte + 3] = mbytes[ibyte + 3] ^ (
|
||||
0xFF & ((G[2] << 8 - ibit) | (G[3] >> ibit))
|
||||
)
|
||||
|
||||
cdef long result = (mbytes[len_mbytes-3] << 16) | (mbytes[len_mbytes-2] << 8) | mbytes[len_mbytes-1]
|
||||
|
||||
return result
|
||||
|
||||
|
||||
|
||||
cpdef long floor(double x):
|
||||
"""Mode-S floor function.
|
||||
|
||||
Defined as the greatest integer value k, such that k <= x
|
||||
For example: floor(3.6) = 3 and floor(-3.6) = -4
|
||||
|
||||
"""
|
||||
return <long> c_floor(x)
|
||||
|
||||
cpdef str icao(str msg):
|
||||
"""Calculate the ICAO address from an Mode-S message."""
|
||||
cdef unsigned char DF = df(msg)
|
||||
cdef long c0, c1
|
||||
|
||||
if DF in (11, 17, 18):
|
||||
addr = msg[2:8]
|
||||
elif DF in (0, 4, 5, 16, 20, 21):
|
||||
c0 = crc(msg, encode=True)
|
||||
c1 = hex2int(msg[-6:])
|
||||
addr = "%06X" % (c0 ^ c1)
|
||||
else:
|
||||
addr = None
|
||||
|
||||
return addr
|
||||
|
||||
|
||||
cpdef bint is_icao_assigned(str icao):
|
||||
"""Check whether the ICAO address is assigned (Annex 10, Vol 3)."""
|
||||
if (icao is None) or (not isinstance(icao, str)) or (len(icao) != 6):
|
||||
return False
|
||||
|
||||
cdef long icaoint = hex2int(icao)
|
||||
|
||||
if 0x200000 < icaoint < 0x27FFFF:
|
||||
return False # AFI
|
||||
if 0x280000 < icaoint < 0x28FFFF:
|
||||
return False # SAM
|
||||
if 0x500000 < icaoint < 0x5FFFFF:
|
||||
return False # EUR, NAT
|
||||
if 0x600000 < icaoint < 0x67FFFF:
|
||||
return False # MID
|
||||
if 0x680000 < icaoint < 0x6F0000:
|
||||
return False # ASIA
|
||||
if 0x900000 < icaoint < 0x9FFFFF:
|
||||
return False # NAM, PAC
|
||||
if 0xB00000 < icaoint < 0xBFFFFF:
|
||||
return False # CAR
|
||||
if 0xD00000 < icaoint < 0xDFFFFF:
|
||||
return False # future
|
||||
if 0xF00000 < icaoint < 0xFFFFFF:
|
||||
return False # future
|
||||
|
||||
return True
|
||||
|
||||
@cython.boundscheck(False)
|
||||
@cython.wraparound(False)
|
||||
cpdef int typecode(str msg):
|
||||
"""Type code of ADS-B message"""
|
||||
if df(msg) not in (17, 18):
|
||||
return -1
|
||||
# return None
|
||||
|
||||
cdef str tcbin = hex2bin(msg[8:10])
|
||||
return bin2int(tcbin[0:5])
|
||||
|
||||
@cython.cdivision(True)
|
||||
cpdef int cprNL(double lat):
|
||||
"""NL() function in CPR decoding."""
|
||||
|
||||
if abs(lat) <= 1e-08:
|
||||
return 59
|
||||
elif abs(abs(lat) - 87) <= 1e-08 + 1e-05 * 87:
|
||||
return 2
|
||||
elif lat > 87 or lat < -87:
|
||||
return 1
|
||||
|
||||
cdef int nz = 15
|
||||
cdef double a = 1 - cos(pi / (2 * nz))
|
||||
cdef double b = cos(pi / 180 * fabs(lat)) ** 2
|
||||
cdef double nl = 2 * pi / (acos(1 - a / b))
|
||||
NL = floor(nl)
|
||||
return NL
|
||||
|
||||
@cython.boundscheck(False)
|
||||
@cython.wraparound(False)
|
||||
cpdef str idcode(str msg):
|
||||
"""Compute identity (squawk code)."""
|
||||
if df(msg) not in [5, 21]:
|
||||
raise RuntimeError("Message must be Downlink Format 5 or 21.")
|
||||
|
||||
squawk_code = squawk(hex2bin(msg)[19:32])
|
||||
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 bytearray _idcode = bytearray(4)
|
||||
cdef unsigned char[:] idcode = _idcode
|
||||
|
||||
cdef unsigned char C1 = mbin[0]
|
||||
cdef unsigned char A1 = mbin[1]
|
||||
cdef unsigned char C2 = mbin[2]
|
||||
cdef unsigned char A2 = mbin[3]
|
||||
cdef unsigned char C4 = mbin[4]
|
||||
cdef unsigned char A4 = mbin[5]
|
||||
# X = mbin[6]
|
||||
cdef unsigned char B1 = mbin[7]
|
||||
cdef unsigned char D1 = mbin[8]
|
||||
cdef unsigned char B2 = mbin[9]
|
||||
cdef unsigned char D2 = mbin[10]
|
||||
cdef unsigned char B4 = mbin[11]
|
||||
cdef unsigned char D4 = mbin[12]
|
||||
|
||||
idcode[0] = int_to_char((char_to_int(A4)*2 + char_to_int(A2))*2 + char_to_int(A1))
|
||||
idcode[1] = int_to_char((char_to_int(B4)*2 + char_to_int(B2))*2 + char_to_int(B1))
|
||||
idcode[2] = int_to_char((char_to_int(C4)*2 + char_to_int(C2))*2 + char_to_int(C1))
|
||||
idcode[3] = int_to_char((char_to_int(D4)*2 + char_to_int(D2))*2 + char_to_int(D1))
|
||||
|
||||
return _idcode.decode()
|
||||
|
||||
|
||||
@cython.boundscheck(False)
|
||||
@cython.wraparound(False)
|
||||
cpdef int altcode(str msg):
|
||||
"""Compute the altitude."""
|
||||
if df(msg) not in [0, 4, 16, 20]:
|
||||
raise RuntimeError("Message must be Downlink Format 0, 4, 16, or 20.")
|
||||
|
||||
alt = altitude(hex2bin(msg)[19:32])
|
||||
return alt
|
||||
|
||||
|
||||
@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 char Mbit = binstr[6]
|
||||
cdef char Qbit = binstr[8]
|
||||
|
||||
cdef int alt = 0
|
||||
cdef bytearray vbin
|
||||
cdef bytearray _graybytes = bytearray(11)
|
||||
cdef unsigned char[:] graybytes = _graybytes
|
||||
|
||||
if bin2int(binstr) == 0:
|
||||
# altitude unknown or invalid
|
||||
alt = -9999
|
||||
|
||||
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
|
||||
if Qbit == 48: # 100ft interval, above 50175ft, "0" -> 48
|
||||
graybytes[8] = mbin[0]
|
||||
graybytes[2] = mbin[1]
|
||||
graybytes[9] = mbin[2]
|
||||
graybytes[3] = mbin[3]
|
||||
graybytes[10] = mbin[4]
|
||||
graybytes[4] = mbin[5]
|
||||
# M = mbin[6]
|
||||
graybytes[5] = mbin[7]
|
||||
# Q = mbin[8]
|
||||
graybytes[6] = mbin[9]
|
||||
graybytes[0] = mbin[10]
|
||||
graybytes[7] = mbin[11]
|
||||
graybytes[1] = mbin[12]
|
||||
|
||||
alt = gray2alt(_graybytes.decode())
|
||||
|
||||
elif Mbit == 49: # unit in meter, "1" -> 49
|
||||
vbin = _mbin[:6] + _mbin[7:]
|
||||
alt = int(bin2int(vbin.decode()) * 3.28084) # convert to ft
|
||||
|
||||
return alt
|
||||
|
||||
|
||||
cpdef int gray2alt(str codestr):
|
||||
cdef str gc500 = codestr[:8]
|
||||
cdef int n500 = gray2int(gc500)
|
||||
|
||||
# in 100-ft step must be converted first
|
||||
cdef str gc100 = codestr[8:]
|
||||
cdef int n100 = gray2int(gc100)
|
||||
|
||||
if n100 in [0, 5, 6]:
|
||||
return -1
|
||||
#return None
|
||||
|
||||
if n100 == 7:
|
||||
n100 = 5
|
||||
|
||||
if n500 % 2:
|
||||
n100 = 6 - n100
|
||||
|
||||
alt = (n500 * 500 + n100 * 100) - 1300
|
||||
return alt
|
||||
|
||||
|
||||
cdef int gray2int(str graystr):
|
||||
"""Convert greycode to binary."""
|
||||
cdef int num = bin2int(graystr)
|
||||
num ^= num >> 8
|
||||
num ^= num >> 4
|
||||
num ^= num >> 2
|
||||
num ^= num >> 1
|
||||
return num
|
||||
|
||||
|
||||
cpdef str data(str msg):
|
||||
"""Return the data frame in the message, bytes 9 to 22."""
|
||||
return msg[8:-6]
|
||||
|
||||
|
||||
cpdef bint allzeros(str msg):
|
||||
"""Check if the data bits are all zeros."""
|
||||
d = hex2bin(data(msg))
|
||||
|
||||
if bin2int(d) > 0:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def wrongstatus(data, sb, msb, lsb):
|
||||
"""Check if the status bit and field bits are consistency.
|
||||
|
||||
This Function is used for checking BDS code versions.
|
||||
|
||||
"""
|
||||
# status bit, most significant bit, least significant bit
|
||||
status = int(data[sb - 1])
|
||||
value = bin2int(data[msb - 1 : lsb])
|
||||
|
||||
if not status:
|
||||
if value != 0:
|
||||
return True
|
||||
|
||||
return False
|
||||
22
pyModeS/common.pyi
Normal file
22
pyModeS/common.pyi
Normal file
@@ -0,0 +1,22 @@
|
||||
from typing import Optional
|
||||
|
||||
def hex2bin(hexstr: str) -> str: ...
|
||||
def bin2int(binstr: str) -> int: ...
|
||||
def hex2int(hexstr: str) -> int: ...
|
||||
def bin2hex(binstr: str) -> str: ...
|
||||
def df(msg: str) -> int: ...
|
||||
def crc(msg: str, encode: bool = False) -> int: ...
|
||||
def floor(x: float) -> float: ...
|
||||
def icao(msg: str) -> Optional[str]: ...
|
||||
def is_icao_assigned(icao: str) -> bool: ...
|
||||
def typecode(msg: str) -> Optional[int]: ...
|
||||
def cprNL(lat: float) -> int: ...
|
||||
def idcode(msg: str) -> str: ...
|
||||
def squawk(binstr: str) -> str: ...
|
||||
def altcode(msg: str) -> Optional[int]: ...
|
||||
def altitude(binstr: str) -> Optional[int]: ...
|
||||
def gray2alt(binstr: str) -> Optional[int]: ...
|
||||
def gray2int(binstr: str) -> int: ...
|
||||
def data(msg: str) -> str: ...
|
||||
def allzeros(msg: str) -> bool: ...
|
||||
def wrongstatus(data: str, sb: int, msb: int, lsb: int) -> bool: ...
|
||||
@@ -1,11 +1,8 @@
|
||||
from __future__ import absolute_import, print_function, division
|
||||
def tell(msg: str) -> None:
|
||||
from .. import common, adsb, commb, bds
|
||||
|
||||
from pyModeS.decoder import adsb, commb, common, bds
|
||||
|
||||
|
||||
def tell(msg):
|
||||
def _print(label, value, unit=None):
|
||||
print("%20s: " % label, end="")
|
||||
print("%28s: " % label, end="")
|
||||
print("%s " % value, end="")
|
||||
if unit:
|
||||
print(unit)
|
||||
@@ -20,16 +17,21 @@ def tell(msg):
|
||||
_print("Downlink Format", df)
|
||||
|
||||
if df == 17:
|
||||
_print("Protocal", "Mode-S Extended Squitter (ADS-B)")
|
||||
_print("Protocol", "Mode-S Extended Squitter (ADS-B)")
|
||||
|
||||
tc = common.typecode(msg)
|
||||
|
||||
if tc is None:
|
||||
_print("ERROR", "Unknown typecode")
|
||||
return
|
||||
|
||||
if 1 <= tc <= 4: # callsign
|
||||
callsign = adsb.callsign(msg)
|
||||
_print("Type", "Identitification and category")
|
||||
_print("Type", "Identification and category")
|
||||
_print("Callsign:", callsign)
|
||||
|
||||
if 5 <= tc <= 8: # surface position
|
||||
_print("Type", "Surface postition")
|
||||
_print("Type", "Surface position")
|
||||
oe = adsb.oe_flag(msg)
|
||||
msgbin = common.hex2bin(msg)
|
||||
cprlat = common.bin2int(msgbin[54:71]) / 131072.0
|
||||
@@ -55,12 +57,14 @@ def tell(msg):
|
||||
|
||||
if tc == 19:
|
||||
_print("Type", "Airborne velocity")
|
||||
spd, trk, vr, t = adsb.velocity(msg)
|
||||
types = {"GS": "Ground speed", "TAS": "True airspeed"}
|
||||
_print("Speed", spd, "knots")
|
||||
_print("Track", trk, "degrees")
|
||||
_print("Vertical rate", vr, "feet/minute")
|
||||
_print("Type", types[t])
|
||||
velocity = adsb.velocity(msg)
|
||||
if velocity is not None:
|
||||
spd, trk, vr, t = velocity
|
||||
types = {"GS": "Ground speed", "TAS": "True airspeed"}
|
||||
_print("Speed", spd, "knots")
|
||||
_print("Track", trk, "degrees")
|
||||
_print("Vertical rate", vr, "feet/minute")
|
||||
_print("Type", types[t])
|
||||
|
||||
if 20 <= tc <= 22: # airborne position
|
||||
_print("Type", "Airborne position (with GNSS altitude)")
|
||||
@@ -74,12 +78,100 @@ def tell(msg):
|
||||
_print("CPR Longitude", cprlon)
|
||||
_print("Altitude", alt, "feet")
|
||||
|
||||
if tc == 29: # target state and status
|
||||
_print("Type", "Target State and Status")
|
||||
subtype = common.bin2int((common.hex2bin(msg)[32:])[5:7])
|
||||
_print("Subtype", subtype)
|
||||
tcas_operational = adsb.tcas_operational(msg)
|
||||
types_29 = {0: "Not Engaged", 1: "Engaged"}
|
||||
tcas_operational_types = {0: "Not Operational", 1: "Operational"}
|
||||
if subtype == 0:
|
||||
emergency_types = {
|
||||
0: "No emergency",
|
||||
1: "General emergency",
|
||||
2: "Lifeguard/medical emergency",
|
||||
3: "Minimum fuel",
|
||||
4: "No communications",
|
||||
5: "Unlawful interference",
|
||||
6: "Downed aircraft",
|
||||
7: "Reserved",
|
||||
}
|
||||
vertical_horizontal_types = {
|
||||
1: "Acquiring mode",
|
||||
2: "Capturing/Maintaining mode",
|
||||
}
|
||||
tcas_ra_types = {0: "Not active", 1: "Active"}
|
||||
alt, alt_source, alt_ref = adsb.target_altitude(msg)
|
||||
angle, angle_type, angle_source = adsb.target_angle(msg)
|
||||
vertical_mode = adsb.vertical_mode(msg)
|
||||
horizontal_mode = adsb.horizontal_mode(msg)
|
||||
tcas_ra = adsb.tcas_ra(msg)
|
||||
emergency_status = adsb.emergency_status(msg)
|
||||
_print("Target altitude", alt, "feet")
|
||||
_print("Altitude source", alt_source)
|
||||
_print("Altitude reference", alt_ref)
|
||||
_print("Angle", angle, "°")
|
||||
_print("Angle Type", angle_type)
|
||||
_print("Angle Source", angle_source)
|
||||
if vertical_mode is not None:
|
||||
_print(
|
||||
"Vertical mode",
|
||||
vertical_horizontal_types[vertical_mode],
|
||||
)
|
||||
if horizontal_mode is not None:
|
||||
_print(
|
||||
"Horizontal mode",
|
||||
vertical_horizontal_types[horizontal_mode],
|
||||
)
|
||||
_print(
|
||||
"TCAS/ACAS",
|
||||
tcas_operational_types[tcas_operational]
|
||||
if tcas_operational
|
||||
else None,
|
||||
)
|
||||
_print("TCAS/ACAS RA", tcas_ra_types[tcas_ra])
|
||||
_print("Emergency status", emergency_types[emergency_status])
|
||||
else:
|
||||
alt, alt_source = adsb.selected_altitude(msg) # type: ignore
|
||||
baro = adsb.baro_pressure_setting(msg)
|
||||
hdg = adsb.selected_heading(msg)
|
||||
autopilot = adsb.autopilot(msg)
|
||||
vnav = adsb.vnav_mode(msg)
|
||||
alt_hold = adsb.altitude_hold_mode(msg)
|
||||
app = adsb.approach_mode(msg)
|
||||
lnav = adsb.lnav_mode(msg)
|
||||
_print("Selected altitude", alt, "feet")
|
||||
_print("Altitude source", alt_source)
|
||||
_print(
|
||||
"Barometric pressure setting",
|
||||
baro,
|
||||
"" if baro is None else "millibars",
|
||||
)
|
||||
_print("Selected Heading", hdg, "°")
|
||||
if not (common.bin2int((common.hex2bin(msg)[32:])[46]) == 0):
|
||||
_print(
|
||||
"Autopilot", types_29[autopilot] if autopilot else None
|
||||
)
|
||||
_print("VNAV mode", types_29[vnav] if vnav else None)
|
||||
_print(
|
||||
"Altitude hold mode",
|
||||
types_29[alt_hold] if alt_hold else None,
|
||||
)
|
||||
_print("Approach mode", types_29[app] if app else None)
|
||||
_print(
|
||||
"TCAS/ACAS",
|
||||
tcas_operational_types[tcas_operational]
|
||||
if tcas_operational
|
||||
else None,
|
||||
)
|
||||
_print("LNAV mode", types_29[lnav] if lnav else None)
|
||||
|
||||
if df == 20:
|
||||
_print("Protocal", "Mode-S Comm-B altitude reply")
|
||||
_print("Protocol", "Mode-S Comm-B altitude reply")
|
||||
_print("Altitude", common.altcode(msg), "feet")
|
||||
|
||||
if df == 21:
|
||||
_print("Protocal", "Mode-S Comm-B identity reply")
|
||||
_print("Protocol", "Mode-S Comm-B identity reply")
|
||||
_print("Squawk code", common.idcode(msg))
|
||||
|
||||
if df == 20 or df == 21:
|
||||
@@ -97,7 +189,7 @@ def tell(msg):
|
||||
}
|
||||
|
||||
BDS = bds.infer(msg, mrar=True)
|
||||
if BDS in labels.keys():
|
||||
if BDS is not None and BDS in labels.keys():
|
||||
_print("BDS", "%s (%s)" % (BDS, labels[BDS]))
|
||||
else:
|
||||
_print("BDS", BDS)
|
||||
|
||||
@@ -1,23 +1,5 @@
|
||||
# Copyright (C) 2018 Junzi Sun (TU Delft)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Decoding Air-Air Surveillance (ACAS) DF=0/16
|
||||
|
||||
[To be implemented]
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
from pyModeS.decoder import common
|
||||
|
||||
@@ -1,88 +1,150 @@
|
||||
# Copyright (C) 2015 Junzi Sun (TU Delft)
|
||||
"""ADS-B module.
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
The ADS-B module also imports functions from the following modules:
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""ADS-B Wrapper.
|
||||
|
||||
The ADS-B wrapper also imports functions from the following modules:
|
||||
|
||||
- pyModeS.decoder.bds.bds05
|
||||
Functions: ``airborne_position``, ``airborne_position_with_ref``, ``altitude``
|
||||
- pyModeS.decoder.bds.bds06
|
||||
Functions: ``surface_position``, ``surface_position_with_ref``, ``surface_velocity``
|
||||
- pyModeS.decoder.bds.bds08
|
||||
Functions: ``category``, ``callsign``
|
||||
- pyModeS.decoder.bds.bds09
|
||||
Functions: ``airborne_velocity``, ``altitude_diff``
|
||||
- bds05: ``airborne_position()``, ``airborne_position_with_ref()``,
|
||||
``altitude()``
|
||||
- bds06: ``surface_position()``, ``surface_position_with_ref()``,
|
||||
``surface_velocity()``
|
||||
- bds08: ``category()``, ``callsign()``
|
||||
- bds09: ``airborne_velocity()``, ``altitude_diff()``
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
from __future__ import annotations
|
||||
from datetime import datetime
|
||||
|
||||
import pyModeS as pms
|
||||
from pyModeS.decoder import common
|
||||
from pyModeS.decoder import uncertainty
|
||||
|
||||
# from pyModeS.decoder.bds import bds05, bds06, bds09
|
||||
from pyModeS.decoder.bds.bds05 import (
|
||||
airborne_position,
|
||||
airborne_position_with_ref,
|
||||
altitude,
|
||||
)
|
||||
from pyModeS.decoder.bds.bds06 import (
|
||||
from .. import common
|
||||
from . import uncertainty
|
||||
from .bds.bds05 import airborne_position, airborne_position_with_ref
|
||||
from .bds.bds05 import altitude as altitude05
|
||||
from .bds.bds06 import (
|
||||
surface_position,
|
||||
surface_position_with_ref,
|
||||
surface_velocity,
|
||||
)
|
||||
from pyModeS.decoder.bds.bds08 import category, callsign
|
||||
from pyModeS.decoder.bds.bds09 import airborne_velocity, altitude_diff
|
||||
from .bds.bds08 import callsign, category
|
||||
from .bds.bds09 import airborne_velocity, altitude_diff
|
||||
from .bds.bds61 import emergency_squawk, emergency_state, is_emergency
|
||||
from .bds.bds62 import (
|
||||
altitude_hold_mode,
|
||||
approach_mode,
|
||||
autopilot,
|
||||
baro_pressure_setting,
|
||||
emergency_status,
|
||||
horizontal_mode,
|
||||
lnav_mode,
|
||||
selected_altitude,
|
||||
selected_heading,
|
||||
target_altitude,
|
||||
target_angle,
|
||||
tcas_operational,
|
||||
tcas_ra,
|
||||
vertical_mode,
|
||||
vnav_mode,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"airborne_position",
|
||||
"airborne_position_with_ref",
|
||||
"altitude05",
|
||||
"surface_position",
|
||||
"surface_position_with_ref",
|
||||
"surface_velocity",
|
||||
"callsign",
|
||||
"category",
|
||||
"airborne_velocity",
|
||||
"altitude_diff",
|
||||
"emergency_squawk",
|
||||
"emergency_state",
|
||||
"is_emergency",
|
||||
"df",
|
||||
"icao",
|
||||
"typecode",
|
||||
"position",
|
||||
"position_with_ref",
|
||||
"altitude",
|
||||
"velocity",
|
||||
"speed_heading",
|
||||
"oe_flag",
|
||||
"version",
|
||||
"nuc_p",
|
||||
"nuc_v",
|
||||
"nic_v1",
|
||||
"nic_v2",
|
||||
"nic_s",
|
||||
"nic_a_c",
|
||||
"nic_b",
|
||||
"nac_p",
|
||||
"nac_v",
|
||||
"sil",
|
||||
"selected_altitude",
|
||||
"target_altitude",
|
||||
"vertical_mode",
|
||||
"horizontal_mode",
|
||||
"selected_heading",
|
||||
"target_angle",
|
||||
"baro_pressure_setting",
|
||||
"autopilot",
|
||||
"vnav_mode",
|
||||
"altitude_hold_mode",
|
||||
"approach_mode",
|
||||
"lnav_mode",
|
||||
"tcas_operational",
|
||||
"tcas_ra",
|
||||
"emergency_status",
|
||||
]
|
||||
|
||||
|
||||
def df(msg):
|
||||
def df(msg: str) -> int:
|
||||
return common.df(msg)
|
||||
|
||||
|
||||
def icao(msg):
|
||||
def icao(msg: str) -> None | str:
|
||||
return common.icao(msg)
|
||||
|
||||
|
||||
def typecode(msg):
|
||||
def typecode(msg: str) -> None | int:
|
||||
return common.typecode(msg)
|
||||
|
||||
|
||||
def position(msg0, msg1, t0, t1, lat_ref=None, lon_ref=None):
|
||||
"""Decode position from a pair of even and odd position message
|
||||
(works with both airborne and surface position messages)
|
||||
def position(
|
||||
msg0: str,
|
||||
msg1: str,
|
||||
t0: int | datetime,
|
||||
t1: int | datetime,
|
||||
lat_ref: None | float = None,
|
||||
lon_ref: None | float = None,
|
||||
) -> None | tuple[float, float]:
|
||||
"""Decode surface or airborne position from a pair of even and odd
|
||||
position messages.
|
||||
|
||||
Note, that to decode surface position using the position message pair,
|
||||
the reference position has to be provided.
|
||||
|
||||
Args:
|
||||
msg0 (string): even message (28 bytes hexadecimal string)
|
||||
msg1 (string): odd message (28 bytes hexadecimal string)
|
||||
msg0 (string): even message (28 hexdigits)
|
||||
msg1 (string): odd message (28 hexdigits)
|
||||
t0 (int): timestamps for the even message
|
||||
t1 (int): timestamps for the odd message
|
||||
lat_ref (float): latitude of reference position
|
||||
lon_ref (float): longitude of reference position
|
||||
|
||||
Returns:
|
||||
(float, float): (latitude, longitude) of the aircraft
|
||||
|
||||
"""
|
||||
tc0 = typecode(msg0)
|
||||
tc1 = typecode(msg1)
|
||||
|
||||
if tc0 is None or tc1 is None:
|
||||
raise RuntimeError("Incorrect or inconsistent message types")
|
||||
|
||||
if 5 <= tc0 <= 8 and 5 <= tc1 <= 8:
|
||||
if (not lat_ref) or (not lon_ref):
|
||||
if lat_ref is None or lon_ref is None:
|
||||
raise RuntimeError(
|
||||
"Surface position encountered, a reference \
|
||||
position lat/lon required. Location of \
|
||||
receiver can be used."
|
||||
"Surface position encountered, a reference position"
|
||||
" lat/lon required. Location of receiver can be used."
|
||||
)
|
||||
else:
|
||||
return surface_position(msg0, msg1, t0, t1, lat_ref, lon_ref)
|
||||
@@ -96,19 +158,22 @@ def position(msg0, msg1, t0, t1, lat_ref=None, lon_ref=None):
|
||||
return airborne_position(msg0, msg1, t0, t1)
|
||||
|
||||
else:
|
||||
raise RuntimeError("incorrect or inconsistant message types")
|
||||
raise RuntimeError("Incorrect or inconsistent message types")
|
||||
|
||||
|
||||
def position_with_ref(msg, lat_ref, lon_ref):
|
||||
"""Decode position with only one message,
|
||||
knowing reference nearby location, such as previously
|
||||
calculated location, ground station, or airport location, etc.
|
||||
Works with both airborne and surface position messages.
|
||||
The reference position shall be with in 180NM (airborne) or 45NM (surface)
|
||||
def position_with_ref(
|
||||
msg: str, lat_ref: float, lon_ref: float
|
||||
) -> tuple[float, float]:
|
||||
"""Decode position with only one message.
|
||||
|
||||
A reference position is required, which can be previously
|
||||
calculated location, ground station, or airport location.
|
||||
The function works with both airborne and surface position messages.
|
||||
The reference position shall be within 180NM (airborne) or 45NM (surface)
|
||||
of the true position.
|
||||
|
||||
Args:
|
||||
msg (string): even message (28 bytes hexadecimal string)
|
||||
msg (str): even message (28 hexdigits)
|
||||
lat_ref: previous known latitude
|
||||
lon_ref: previous known longitude
|
||||
|
||||
@@ -118,6 +183,9 @@ def position_with_ref(msg, lat_ref, lon_ref):
|
||||
|
||||
tc = typecode(msg)
|
||||
|
||||
if tc is None:
|
||||
raise RuntimeError("incorrect or inconsistent message types")
|
||||
|
||||
if 5 <= tc <= 8:
|
||||
return surface_position_with_ref(msg, lat_ref, lon_ref)
|
||||
|
||||
@@ -125,93 +193,94 @@ def position_with_ref(msg, lat_ref, lon_ref):
|
||||
return airborne_position_with_ref(msg, lat_ref, lon_ref)
|
||||
|
||||
else:
|
||||
raise RuntimeError("incorrect or inconsistant message types")
|
||||
raise RuntimeError("incorrect or inconsistent message types")
|
||||
|
||||
|
||||
def altitude(msg):
|
||||
"""Decode aircraft altitude
|
||||
def altitude(msg: str) -> None | float:
|
||||
"""Decode aircraft altitude.
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
int: altitude in feet
|
||||
"""
|
||||
|
||||
"""
|
||||
tc = typecode(msg)
|
||||
|
||||
if tc < 5 or tc == 19 or tc > 22:
|
||||
if tc is None or tc < 5 or tc == 19 or tc > 22:
|
||||
raise RuntimeError("%s: Not a position message" % msg)
|
||||
|
||||
if tc >= 5 and tc <= 8:
|
||||
elif tc >= 5 and tc <= 8:
|
||||
# surface position, altitude 0
|
||||
return 0
|
||||
|
||||
msgbin = common.hex2bin(msg)
|
||||
q = msgbin[47]
|
||||
if q:
|
||||
n = common.bin2int(msgbin[40:47] + msgbin[48:52])
|
||||
alt = n * 25 - 1000
|
||||
return alt
|
||||
else:
|
||||
return None
|
||||
# airborn position
|
||||
return altitude05(msg)
|
||||
|
||||
|
||||
def velocity(msg, rtn_sources=False):
|
||||
def velocity(
|
||||
msg: str, source: bool = False
|
||||
) -> None | tuple[None | float, None | float, None | int, str]:
|
||||
"""Calculate the speed, heading, and vertical rate
|
||||
(handles both airborne or surface message)
|
||||
(handles both airborne or surface message).
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
rtn_source (boolean): If the function will return
|
||||
the sources for direction of travel and vertical
|
||||
rate. This will change the return value from a four
|
||||
element array to a six element array.
|
||||
msg (str): 28 hexdigits string
|
||||
source (boolean): Include direction and vertical rate sources in return.
|
||||
Default to False.
|
||||
If set to True, the function will return six value instead of four.
|
||||
|
||||
Returns:
|
||||
(int, float, int, string, string, string): speed (kt),
|
||||
ground track or heading (degree),
|
||||
rate of climb/descent (ft/min), speed type
|
||||
('GS' for ground speed, 'AS' for airspeed),
|
||||
direction source ('true_north' for ground track / true north
|
||||
as refrence, 'mag_north' for magnetic north as reference),
|
||||
rate of climb/descent source ('Baro' for barometer, 'GNSS'
|
||||
for GNSS constellation).
|
||||
|
||||
In the case of surface messages, None will be put in place
|
||||
for vertical rate and its respective sources.
|
||||
int, float, int, string, [string], [string]:
|
||||
- Speed (kt)
|
||||
- Angle (degree), either ground track or heading
|
||||
- Vertical rate (ft/min)
|
||||
- Speed type ('GS' for ground speed, 'AS' for airspeed)
|
||||
- [Optional] Direction source ('TRUE_NORTH' or 'MAGNETIC_NORTH')
|
||||
- [Optional] Vertical rate source ('BARO' or 'GNSS')
|
||||
|
||||
For surface messages, vertical rate and its respective sources are set
|
||||
to None.
|
||||
|
||||
"""
|
||||
tc = typecode(msg)
|
||||
error = "incorrect or inconsistent message types, expecting 4<TC<9 or TC=19"
|
||||
if tc is None:
|
||||
raise RuntimeError(error)
|
||||
|
||||
if 5 <= typecode(msg) <= 8:
|
||||
return surface_velocity(msg, rtn_sources)
|
||||
if 5 <= tc <= 8:
|
||||
return surface_velocity(msg, source)
|
||||
|
||||
elif typecode(msg) == 19:
|
||||
return airborne_velocity(msg, rtn_sources)
|
||||
elif tc == 19:
|
||||
return airborne_velocity(msg, source)
|
||||
|
||||
else:
|
||||
raise RuntimeError(
|
||||
"incorrect or inconsistant message types, expecting 4<TC<9 or TC=19"
|
||||
)
|
||||
raise RuntimeError(error)
|
||||
|
||||
|
||||
def speed_heading(msg):
|
||||
def speed_heading(msg: str) -> None | tuple[None | float, None | float]:
|
||||
"""Get speed and ground track (or heading) from the velocity message
|
||||
(handles both airborne or surface message)
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
(int, float): speed (kt), ground track or heading (degree)
|
||||
"""
|
||||
spd, trk_or_hdg, rocd, tag = velocity(msg)
|
||||
decoded = velocity(msg)
|
||||
if decoded is None:
|
||||
return None
|
||||
spd, trk_or_hdg, rocd, tag = decoded
|
||||
return spd, trk_or_hdg
|
||||
|
||||
|
||||
def oe_flag(msg):
|
||||
def oe_flag(msg: str) -> int:
|
||||
"""Check the odd/even flag. Bit 54, 0 for even, 1 for odd.
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
msg (str): 28 hexdigits string
|
||||
Returns:
|
||||
int: 0 or 1, for even or odd frame
|
||||
"""
|
||||
@@ -219,11 +288,11 @@ def oe_flag(msg):
|
||||
return int(msgbin[53])
|
||||
|
||||
|
||||
def version(msg):
|
||||
def version(msg: str) -> int:
|
||||
"""ADS-B Version
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string, TC = 31
|
||||
msg (str): 28 hexdigits string, TC = 31
|
||||
|
||||
Returns:
|
||||
int: version number
|
||||
@@ -241,13 +310,15 @@ def version(msg):
|
||||
return version
|
||||
|
||||
|
||||
def nuc_p(msg):
|
||||
"""Calculate NUCp, Navigation Uncertainty Category - Position (ADS-B version 1)
|
||||
def nuc_p(msg: str) -> tuple[int, None | float, None | int, None | int]:
|
||||
"""Calculate NUCp, Navigation Uncertainty Category - Position
|
||||
(ADS-B version 1)
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string,
|
||||
msg (str): 28 hexdigits string,
|
||||
|
||||
Returns:
|
||||
int: NUCp, Navigation Uncertainty Category (position)
|
||||
int: Horizontal Protection Limit
|
||||
int: 95% Containment Radius - Horizontal (meters)
|
||||
int: 95% Containment Radius - Vertical (meters)
|
||||
@@ -255,7 +326,7 @@ def nuc_p(msg):
|
||||
"""
|
||||
tc = typecode(msg)
|
||||
|
||||
if typecode(msg) < 5 or typecode(msg) > 22:
|
||||
if tc is None or tc < 5 or tc is None or tc > 22:
|
||||
raise RuntimeError(
|
||||
"%s: Not a surface position message (5<TC<8), \
|
||||
airborne position message (8<TC<19), \
|
||||
@@ -263,27 +334,36 @@ def nuc_p(msg):
|
||||
% msg
|
||||
)
|
||||
|
||||
try:
|
||||
NUCp = uncertainty.TC_NUCp_lookup[tc]
|
||||
HPL = uncertainty.NUCp[NUCp]["HPL"]
|
||||
RCu = uncertainty.NUCp[NUCp]["RCu"]
|
||||
RCv = uncertainty.NUCp[NUCp]["RCv"]
|
||||
except KeyError:
|
||||
NUCp = uncertainty.TC_NUCp_lookup[tc]
|
||||
index = uncertainty.NUCp.get(NUCp, None)
|
||||
|
||||
if index is not None:
|
||||
HPL = index["HPL"]
|
||||
RCu = index["RCu"]
|
||||
RCv = index["RCv"]
|
||||
else:
|
||||
HPL, RCu, RCv = uncertainty.NA, uncertainty.NA, uncertainty.NA
|
||||
|
||||
if tc in [20, 21]:
|
||||
RCv = uncertainty.NA
|
||||
RCv = uncertainty.NA
|
||||
|
||||
return HPL, RCu, RCv
|
||||
# RCv only available for GNSS height
|
||||
if tc == 20:
|
||||
RCv = 4
|
||||
elif tc == 21:
|
||||
RCv = 15
|
||||
|
||||
return NUCp, HPL, RCu, RCv
|
||||
|
||||
|
||||
def nuc_v(msg):
|
||||
"""Calculate NUCv, Navigation Uncertainty Category - Velocity (ADS-B version 1)
|
||||
def nuc_v(msg: str) -> tuple[int, None | float, None | float]:
|
||||
"""Calculate NUCv, Navigation Uncertainty Category - Velocity
|
||||
(ADS-B version 1)
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string,
|
||||
msg (str): 28 hexdigits string,
|
||||
|
||||
Returns:
|
||||
int: NUCv, Navigation Uncertainty Category (velocity)
|
||||
int or string: 95% Horizontal Velocity Error
|
||||
int or string: 95% Vertical Velocity Error
|
||||
"""
|
||||
@@ -296,28 +376,31 @@ def nuc_v(msg):
|
||||
|
||||
msgbin = common.hex2bin(msg)
|
||||
NUCv = common.bin2int(msgbin[42:45])
|
||||
index = uncertainty.NUCv.get(NUCv, None)
|
||||
|
||||
try:
|
||||
HVE = uncertainty.NUCv[NUCv]["HVE"]
|
||||
VVE = uncertainty.NUCv[NUCv]["VVE"]
|
||||
except KeyError:
|
||||
if index is not None:
|
||||
HVE = index["HVE"]
|
||||
VVE = index["VVE"]
|
||||
else:
|
||||
HVE, VVE = uncertainty.NA, uncertainty.NA
|
||||
|
||||
return HVE, VVE
|
||||
return NUCv, HVE, VVE
|
||||
|
||||
|
||||
def nic_v1(msg, NICs):
|
||||
def nic_v1(msg: str, NICs: int) -> tuple[int, None | float, None | float]:
|
||||
"""Calculate NIC, navigation integrity category, for ADS-B version 1
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
msg (str): 28 hexdigits string
|
||||
NICs (int or string): NIC supplement
|
||||
|
||||
Returns:
|
||||
int: NIC, Navigation Integrity Category
|
||||
int or string: Horizontal Radius of Containment
|
||||
int or string: Vertical Protection Limit
|
||||
"""
|
||||
if typecode(msg) < 5 or typecode(msg) > 22:
|
||||
tc = typecode(msg)
|
||||
if tc is None or tc < 5 or tc > 22:
|
||||
raise RuntimeError(
|
||||
"%s: Not a surface position message (5<TC<8), \
|
||||
airborne position message (8<TC<19), \
|
||||
@@ -325,33 +408,37 @@ def nic_v1(msg, NICs):
|
||||
% msg
|
||||
)
|
||||
|
||||
tc = typecode(msg)
|
||||
NIC = uncertainty.TC_NICv1_lookup[tc]
|
||||
|
||||
if isinstance(NIC, dict):
|
||||
NIC = NIC[NICs]
|
||||
|
||||
try:
|
||||
Rc = uncertainty.NICv1[NIC][NICs]["Rc"]
|
||||
VPL = uncertainty.NICv1[NIC][NICs]["VPL"]
|
||||
except KeyError:
|
||||
Rc, VPL = uncertainty.NA, uncertainty.NA
|
||||
d_index = uncertainty.NICv1.get(NIC, None)
|
||||
Rc, VPL = uncertainty.NA, uncertainty.NA
|
||||
|
||||
return Rc, VPL
|
||||
if d_index is not None:
|
||||
index = d_index.get(NICs, None)
|
||||
if index is not None:
|
||||
Rc = index["Rc"]
|
||||
VPL = index["VPL"]
|
||||
|
||||
return NIC, Rc, VPL
|
||||
|
||||
|
||||
def nic_v2(msg, NICa, NICbc):
|
||||
def nic_v2(msg: str, NICa: int, NICbc: int) -> tuple[int, int]:
|
||||
"""Calculate NIC, navigation integrity category, for ADS-B version 2
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
msg (str): 28 hexdigits string
|
||||
NICa (int or string): NIC supplement - A
|
||||
NICbc (int or srting): NIC supplement - B or C
|
||||
NICbc (int or string): NIC supplement - B or C
|
||||
|
||||
Returns:
|
||||
int: NIC, Navigation Integrity Category
|
||||
int or string: Horizontal Radius of Containment
|
||||
"""
|
||||
if typecode(msg) < 5 or typecode(msg) > 22:
|
||||
tc = typecode(msg)
|
||||
if tc is None or tc < 5 or tc > 22:
|
||||
raise RuntimeError(
|
||||
"%s: Not a surface position message (5<TC<8), \
|
||||
airborne position message (8<TC<19), \
|
||||
@@ -359,7 +446,6 @@ def nic_v2(msg, NICa, NICbc):
|
||||
% msg
|
||||
)
|
||||
|
||||
tc = typecode(msg)
|
||||
NIC = uncertainty.TC_NICv2_lookup[tc]
|
||||
|
||||
if 20 <= tc <= 22:
|
||||
@@ -375,14 +461,14 @@ def nic_v2(msg, NICa, NICbc):
|
||||
except KeyError:
|
||||
Rc = uncertainty.NA
|
||||
|
||||
return Rc
|
||||
return NIC, Rc # type: ignore
|
||||
|
||||
|
||||
def nic_s(msg):
|
||||
def nic_s(msg: str) -> int:
|
||||
"""Obtain NIC supplement bit, TC=31 message
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
int: NICs number (0 or 1)
|
||||
@@ -400,11 +486,11 @@ def nic_s(msg):
|
||||
return nic_s
|
||||
|
||||
|
||||
def nic_a_c(msg):
|
||||
def nic_a_c(msg: str) -> tuple[int, int]:
|
||||
"""Obtain NICa/c, navigation integrity category supplements a and c
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
(int, int): NICa and NICc number (0 or 1)
|
||||
@@ -423,18 +509,18 @@ def nic_a_c(msg):
|
||||
return nic_a, nic_c
|
||||
|
||||
|
||||
def nic_b(msg):
|
||||
def nic_b(msg: str) -> int:
|
||||
"""Obtain NICb, navigation integrity category supplement-b
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
int: NICb number (0 or 1)
|
||||
"""
|
||||
tc = typecode(msg)
|
||||
|
||||
if tc < 9 or tc > 18:
|
||||
if tc is None or tc < 9 or tc > 18:
|
||||
raise RuntimeError(
|
||||
"%s: Not a airborne position message, expecting 8<TC<19" % msg
|
||||
)
|
||||
@@ -445,15 +531,18 @@ def nic_b(msg):
|
||||
return nic_b
|
||||
|
||||
|
||||
def nac_p(msg):
|
||||
def nac_p(msg: str) -> tuple[int, int | None, int | None]:
|
||||
"""Calculate NACp, Navigation Accuracy Category - Position
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string, TC = 29 or 31
|
||||
msg (str): 28 hexdigits string, TC = 29 or 31
|
||||
|
||||
Returns:
|
||||
int or string: 95% horizontal accuracy bounds, Estimated Position Uncertainty
|
||||
int or string: 95% vertical accuracy bounds, Vertical Estimated Position Uncertainty
|
||||
int: NACp, Navigation Accuracy Category (position)
|
||||
int or string: 95% horizontal accuracy bounds,
|
||||
Estimated Position Uncertainty
|
||||
int or string: 95% vertical accuracy bounds,
|
||||
Vertical Estimated Position Uncertainty
|
||||
"""
|
||||
tc = typecode(msg)
|
||||
|
||||
@@ -477,18 +566,21 @@ def nac_p(msg):
|
||||
except KeyError:
|
||||
EPU, VEPU = uncertainty.NA, uncertainty.NA
|
||||
|
||||
return EPU, VEPU
|
||||
return NACp, EPU, VEPU
|
||||
|
||||
|
||||
def nac_v(msg):
|
||||
def nac_v(msg: str) -> tuple[int, float | None, float | None]:
|
||||
"""Calculate NACv, Navigation Accuracy Category - Velocity
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string, TC = 19
|
||||
msg (str): 28 hexdigits string, TC = 19
|
||||
|
||||
Returns:
|
||||
int or string: 95% horizontal accuracy bounds for velocity, Horizontal Figure of Merit
|
||||
int or string: 95% vertical accuracy bounds for velocity, Vertical Figure of Merit
|
||||
int: NACv, Navigation Accuracy Category (velocity)
|
||||
int or string: 95% horizontal accuracy bounds for velocity,
|
||||
Horizontal Figure of Merit
|
||||
int or string: 95% vertical accuracy bounds for velocity,
|
||||
Vertical Figure of Merit
|
||||
"""
|
||||
tc = typecode(msg)
|
||||
|
||||
@@ -506,25 +598,30 @@ def nac_v(msg):
|
||||
except KeyError:
|
||||
HFOMr, VFOMr = uncertainty.NA, uncertainty.NA
|
||||
|
||||
return HFOMr, VFOMr
|
||||
return NACv, HFOMr, VFOMr
|
||||
|
||||
|
||||
def sil(msg, version):
|
||||
def sil(
|
||||
msg: str,
|
||||
version: None | int,
|
||||
) -> tuple[float | None, float | None, str]:
|
||||
"""Calculate SIL, Surveillance Integrity Level
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string with TC = 29, 31
|
||||
msg (str): 28 hexdigits string with TC = 29, 31
|
||||
|
||||
Returns:
|
||||
int or string: Probability of exceeding Horizontal Radius of Containment RCu
|
||||
int or string: Probability of exceeding Vertical Integrity Containment Region VPL
|
||||
int or string:
|
||||
Probability of exceeding Horizontal Radius of Containment RCu
|
||||
int or string:
|
||||
Probability of exceeding Vertical Integrity Containment Region VPL
|
||||
string: SIL supplement based on per "hour" or "sample", or 'unknown'
|
||||
"""
|
||||
tc = typecode(msg)
|
||||
|
||||
if tc not in [29, 31]:
|
||||
raise RuntimeError(
|
||||
"%s: Not a target state and status messag, \
|
||||
"%s: Not a target state and status message, \
|
||||
or operation status message, expecting TC = 29 or 31"
|
||||
% msg
|
||||
)
|
||||
|
||||
@@ -1,23 +1,98 @@
|
||||
# Copyright (C) 2018 Junzi Sun (TU Delft)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Decoding all call replies DF=11
|
||||
|
||||
[To be implemented]
|
||||
Decode all-call reply messages, with downlink format 11
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
from pyModeS.decoder import common
|
||||
|
||||
from __future__ import annotations
|
||||
from typing import Callable, TypeVar
|
||||
|
||||
from .. import common
|
||||
|
||||
T = TypeVar("T")
|
||||
F = Callable[[str], T]
|
||||
|
||||
|
||||
def _checkdf(func: F[T]) -> F[T]:
|
||||
|
||||
"""Ensure downlink format is 11."""
|
||||
|
||||
def wrapper(msg: str) -> T:
|
||||
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: str) -> None | str:
|
||||
"""Decode transponder code (ICAO address).
|
||||
|
||||
Args:
|
||||
msg (str): 14 hexdigits string
|
||||
Returns:
|
||||
string: ICAO address
|
||||
|
||||
"""
|
||||
return common.icao(msg)
|
||||
|
||||
|
||||
@_checkdf
|
||||
def interrogator(msg: str) -> str:
|
||||
"""Decode interrogator identifier code.
|
||||
|
||||
Args:
|
||||
msg (str): 14 hexdigits string
|
||||
Returns:
|
||||
int: interrogator identifier code
|
||||
|
||||
"""
|
||||
# the CRC remainder contains the CL and IC field.
|
||||
# the 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: str) -> tuple[int, None | str]:
|
||||
"""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
|
||||
|
||||
@@ -18,16 +18,13 @@
|
||||
Common functions for Mode-S decoding
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
from typing import Optional
|
||||
|
||||
import numpy as np
|
||||
|
||||
from pyModeS.extra import aero
|
||||
from pyModeS.decoder import common
|
||||
from pyModeS.decoder.bds import (
|
||||
bds05,
|
||||
bds06,
|
||||
bds08,
|
||||
bds09,
|
||||
from ... import common
|
||||
from ...extra import aero
|
||||
from . import ( # noqa: F401
|
||||
bds10,
|
||||
bds17,
|
||||
bds20,
|
||||
@@ -36,22 +33,26 @@ from pyModeS.decoder.bds import (
|
||||
bds44,
|
||||
bds45,
|
||||
bds50,
|
||||
bds53,
|
||||
bds60,
|
||||
bds61,
|
||||
bds62,
|
||||
)
|
||||
|
||||
|
||||
def is50or60(msg, spd_ref, trk_ref, alt_ref):
|
||||
def is50or60(
|
||||
msg: str, spd_ref: float, trk_ref: float, alt_ref: float
|
||||
) -> Optional[str]:
|
||||
"""Use reference ground speed and trk to determine BDS50 and DBS60.
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
msg (str): 28 hexdigits string
|
||||
spd_ref (float): reference speed (ADS-B ground speed), kts
|
||||
trk_ref (float): reference track (ADS-B track angle), deg
|
||||
alt_ref (float): reference altitude (ADS-B altitude), ft
|
||||
|
||||
Returns:
|
||||
String or None: BDS version, or possible versions, or None if nothing matches.
|
||||
String or None: BDS version, or possible versions,
|
||||
or None if nothing matches.
|
||||
|
||||
"""
|
||||
|
||||
@@ -60,25 +61,34 @@ def is50or60(msg, spd_ref, trk_ref, alt_ref):
|
||||
vy = v * np.cos(np.radians(angle))
|
||||
return vx, vy
|
||||
|
||||
# message must be both BDS 50 and 60 before processing
|
||||
if not (bds50.is50(msg) and bds60.is60(msg)):
|
||||
return None
|
||||
|
||||
h50 = bds50.trk50(msg)
|
||||
v50 = bds50.gs50(msg)
|
||||
|
||||
if h50 is None or v50 is None:
|
||||
return "BDS50,BDS60"
|
||||
|
||||
# --- assuming BDS60 ---
|
||||
h60 = bds60.hdg60(msg)
|
||||
m60 = bds60.mach60(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):
|
||||
return "BDS50,BDS60"
|
||||
|
||||
m60 = np.nan if m60 is None else m60
|
||||
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)
|
||||
XY6m = vxy(aero.mach2tas(m60, alt_ref * aero.ft), h60)
|
||||
XY6i = vxy(aero.cas2tas(i60 * aero.kts, alt_ref * aero.ft), h60)
|
||||
@@ -104,15 +114,17 @@ def is50or60(msg, spd_ref, trk_ref, alt_ref):
|
||||
return BDS
|
||||
|
||||
|
||||
def infer(msg, mrar=False):
|
||||
def infer(msg: str, mrar: bool = False) -> Optional[str]:
|
||||
"""Estimate the most likely BDS code of an message.
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
mrar (bool): Also infer MRAR (BDS 44) and MHR (BDS 45). Defaults to False.
|
||||
msg (str): 28 hexdigits string
|
||||
mrar (bool): Also infer MRAR (BDS 44) and MHR (BDS 45).
|
||||
Defaults to False.
|
||||
|
||||
Returns:
|
||||
String or None: BDS version, or possible versions, or None if nothing matches.
|
||||
String or None: BDS version, or possible versions,
|
||||
or None if nothing matches.
|
||||
|
||||
"""
|
||||
df = common.df(msg)
|
||||
@@ -123,9 +135,11 @@ def infer(msg, mrar=False):
|
||||
# For ADS-B / Mode-S extended squitter
|
||||
if df == 17:
|
||||
tc = common.typecode(msg)
|
||||
if tc is None:
|
||||
return None
|
||||
|
||||
if 1 <= tc <= 4:
|
||||
return "BDS08" # indentification and category
|
||||
return "BDS08" # identification and category
|
||||
if 5 <= tc <= 8:
|
||||
return "BDS06" # surface movement
|
||||
if 9 <= tc <= 18:
|
||||
|
||||
@@ -1,36 +1,24 @@
|
||||
# Copyright (C) 2018 Junzi Sun (TU Delft)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
# ------------------------------------------
|
||||
# BDS 0,5
|
||||
# ADS-B TC=9-18
|
||||
# Airborn position
|
||||
# Airborne position
|
||||
# ------------------------------------------
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
from pyModeS.decoder import common
|
||||
from datetime import datetime
|
||||
|
||||
from ... import common
|
||||
|
||||
|
||||
def airborne_position(msg0, msg1, t0, t1):
|
||||
"""Decode airborn position from a pair of even and odd position message
|
||||
def airborne_position(
|
||||
msg0: str, msg1: str, t0: int | datetime, t1: int | datetime
|
||||
) -> None | tuple[float, float]:
|
||||
"""Decode airborne position from a pair of even and odd position message
|
||||
|
||||
Args:
|
||||
msg0 (string): even message (28 bytes hexadecimal string)
|
||||
msg1 (string): odd message (28 bytes hexadecimal string)
|
||||
msg0 (string): even message (28 hexdigits)
|
||||
msg1 (string): odd message (28 hexdigits)
|
||||
t0 (int): timestamps for the even message
|
||||
t1 (int): timestamps for the odd message
|
||||
|
||||
@@ -52,13 +40,13 @@ def airborne_position(msg0, msg1, t0, t1):
|
||||
raise RuntimeError("Both even and odd CPR frames are required.")
|
||||
|
||||
# 131072 is 2^17, since CPR lat and lon are 17 bits each.
|
||||
cprlat_even = common.bin2int(mb0[22:39]) / 131072.0
|
||||
cprlon_even = common.bin2int(mb0[39:56]) / 131072.0
|
||||
cprlat_odd = common.bin2int(mb1[22:39]) / 131072.0
|
||||
cprlon_odd = common.bin2int(mb1[39:56]) / 131072.0
|
||||
cprlat_even = common.bin2int(mb0[22:39]) / 131072
|
||||
cprlon_even = common.bin2int(mb0[39:56]) / 131072
|
||||
cprlat_odd = common.bin2int(mb1[22:39]) / 131072
|
||||
cprlon_odd = common.bin2int(mb1[39:56]) / 131072
|
||||
|
||||
air_d_lat_even = 360.0 / 60
|
||||
air_d_lat_odd = 360.0 / 59
|
||||
air_d_lat_even = 360 / 60
|
||||
air_d_lat_odd = 360 / 59
|
||||
|
||||
# compute latitude index 'j'
|
||||
j = common.floor(59 * cprlat_even - 60 * cprlat_odd + 0.5)
|
||||
@@ -77,33 +65,36 @@ def airborne_position(msg0, msg1, t0, t1):
|
||||
return None
|
||||
|
||||
# compute ni, longitude index m, and longitude
|
||||
if t0 > t1:
|
||||
# (people pass int+int or datetime+datetime)
|
||||
if t0 > t1: # type: ignore
|
||||
lat = lat_even
|
||||
nl = common.cprNL(lat)
|
||||
ni = max(common.cprNL(lat) - 0, 1)
|
||||
m = common.floor(cprlon_even * (nl - 1) - cprlon_odd * nl + 0.5)
|
||||
lon = (360.0 / ni) * (m % ni + cprlon_even)
|
||||
lon = (360 / ni) * (m % ni + cprlon_even)
|
||||
else:
|
||||
lat = lat_odd
|
||||
nl = common.cprNL(lat)
|
||||
ni = max(common.cprNL(lat) - 1, 1)
|
||||
m = common.floor(cprlon_even * (nl - 1) - cprlon_odd * nl + 0.5)
|
||||
lon = (360.0 / ni) * (m % ni + cprlon_odd)
|
||||
lon = (360 / ni) * (m % ni + cprlon_odd)
|
||||
|
||||
if lon > 180:
|
||||
lon = lon - 360
|
||||
|
||||
return round(lat, 5), round(lon, 5)
|
||||
return lat, lon
|
||||
|
||||
|
||||
def airborne_position_with_ref(msg, lat_ref, lon_ref):
|
||||
def airborne_position_with_ref(
|
||||
msg: str, lat_ref: float, lon_ref: float
|
||||
) -> tuple[float, float]:
|
||||
"""Decode airborne position with only one message,
|
||||
knowing reference nearby location, such as previously calculated location,
|
||||
ground station, or airport location, etc. The reference position shall
|
||||
be with in 180NM of the true position.
|
||||
be within 180NM of the true position.
|
||||
|
||||
Args:
|
||||
msg (string): even message (28 bytes hexadecimal string)
|
||||
msg (str): even message (28 hexdigits)
|
||||
lat_ref: previous known latitude
|
||||
lon_ref: previous known longitude
|
||||
|
||||
@@ -113,11 +104,11 @@ def airborne_position_with_ref(msg, lat_ref, lon_ref):
|
||||
|
||||
mb = common.hex2bin(msg)[32:]
|
||||
|
||||
cprlat = common.bin2int(mb[22:39]) / 131072.0
|
||||
cprlon = common.bin2int(mb[39:56]) / 131072.0
|
||||
cprlat = common.bin2int(mb[22:39]) / 131072
|
||||
cprlon = common.bin2int(mb[39:56]) / 131072
|
||||
|
||||
i = int(mb[21])
|
||||
d_lat = 360.0 / 59 if i else 360.0 / 60
|
||||
d_lat = 360 / 59 if i else 360 / 60
|
||||
|
||||
j = common.floor(lat_ref / d_lat) + common.floor(
|
||||
0.5 + ((lat_ref % d_lat) / d_lat) - cprlat
|
||||
@@ -128,9 +119,9 @@ def airborne_position_with_ref(msg, lat_ref, lon_ref):
|
||||
ni = common.cprNL(lat) - i
|
||||
|
||||
if ni > 0:
|
||||
d_lon = 360.0 / ni
|
||||
d_lon = 360 / ni
|
||||
else:
|
||||
d_lon = 360.0
|
||||
d_lon = 360
|
||||
|
||||
m = common.floor(lon_ref / d_lon) + common.floor(
|
||||
0.5 + ((lon_ref % d_lon) / d_lon) - cprlon
|
||||
@@ -138,14 +129,14 @@ def airborne_position_with_ref(msg, lat_ref, lon_ref):
|
||||
|
||||
lon = d_lon * (m + cprlon)
|
||||
|
||||
return round(lat, 5), round(lon, 5)
|
||||
return lat, lon
|
||||
|
||||
|
||||
def altitude(msg):
|
||||
def altitude(msg: str) -> None | int:
|
||||
"""Decode aircraft altitude
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
int: altitude in feet
|
||||
@@ -153,21 +144,14 @@ def altitude(msg):
|
||||
|
||||
tc = common.typecode(msg)
|
||||
|
||||
if tc < 9 or tc == 19 or tc > 22:
|
||||
raise RuntimeError("%s: Not a airborn position message" % msg)
|
||||
if tc is None or tc < 9 or tc == 19 or tc > 22:
|
||||
raise RuntimeError("%s: Not an airborne position message" % msg)
|
||||
|
||||
mb = common.hex2bin(msg)[32:]
|
||||
altbin = mb[8:20]
|
||||
|
||||
if tc < 19:
|
||||
# barometric altitude
|
||||
q = mb[15]
|
||||
if q:
|
||||
n = common.bin2int(mb[8:15] + mb[16:20])
|
||||
alt = n * 25 - 1000
|
||||
else:
|
||||
alt = None
|
||||
altcode = altbin[0:6] + "0" + altbin[6:]
|
||||
return common.altitude(altcode)
|
||||
else:
|
||||
# GNSS altitude, meters -> feet
|
||||
alt = common.bin2int(mb[8:20]) * 3.28084
|
||||
|
||||
return alt
|
||||
return common.bin2int(altbin) * 3.28084 # type: ignore
|
||||
|
||||
@@ -1,37 +1,30 @@
|
||||
# Copyright (C) 2018 Junzi Sun (TU Delft)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
# ------------------------------------------
|
||||
# BDS 0,6
|
||||
# ADS-B TC=5-8
|
||||
# Surface position
|
||||
# Surface movement
|
||||
# ------------------------------------------
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
from pyModeS.decoder import common
|
||||
import math
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from ... import common
|
||||
|
||||
|
||||
def surface_position(msg0, msg1, t0, t1, lat_ref, lon_ref):
|
||||
def surface_position(
|
||||
msg0: str,
|
||||
msg1: str,
|
||||
t0: int | datetime,
|
||||
t1: int | datetime,
|
||||
lat_ref: float,
|
||||
lon_ref: float,
|
||||
) -> None | tuple[float, float]:
|
||||
"""Decode surface position from a pair of even and odd position message,
|
||||
the lat/lon of receiver must be provided to yield the correct solution.
|
||||
|
||||
Args:
|
||||
msg0 (string): even message (28 bytes hexadecimal string)
|
||||
msg1 (string): odd message (28 bytes hexadecimal string)
|
||||
msg0 (string): even message (28 hexdigits)
|
||||
msg1 (string): odd message (28 hexdigits)
|
||||
t0 (int): timestamps for the even message
|
||||
t1 (int): timestamps for the odd message
|
||||
lat_ref (float): latitude of the receiver
|
||||
@@ -45,13 +38,13 @@ def surface_position(msg0, msg1, t0, t1, lat_ref, lon_ref):
|
||||
msgbin1 = common.hex2bin(msg1)
|
||||
|
||||
# 131072 is 2^17, since CPR lat and lon are 17 bits each.
|
||||
cprlat_even = common.bin2int(msgbin0[54:71]) / 131072.0
|
||||
cprlon_even = common.bin2int(msgbin0[71:88]) / 131072.0
|
||||
cprlat_odd = common.bin2int(msgbin1[54:71]) / 131072.0
|
||||
cprlon_odd = common.bin2int(msgbin1[71:88]) / 131072.0
|
||||
cprlat_even = common.bin2int(msgbin0[54:71]) / 131072
|
||||
cprlon_even = common.bin2int(msgbin0[71:88]) / 131072
|
||||
cprlat_odd = common.bin2int(msgbin1[54:71]) / 131072
|
||||
cprlon_odd = common.bin2int(msgbin1[71:88]) / 131072
|
||||
|
||||
air_d_lat_even = 90.0 / 60
|
||||
air_d_lat_odd = 90.0 / 59
|
||||
air_d_lat_even = 90 / 60
|
||||
air_d_lat_odd = 90 / 59
|
||||
|
||||
# compute latitude index 'j'
|
||||
j = common.floor(59 * cprlat_even - 60 * cprlat_odd + 0.5)
|
||||
@@ -61,8 +54,8 @@ def surface_position(msg0, msg1, t0, t1, lat_ref, lon_ref):
|
||||
lat_odd_n = float(air_d_lat_odd * (j % 59 + cprlat_odd))
|
||||
|
||||
# solution for north hemisphere
|
||||
lat_even_s = lat_even_n - 90.0
|
||||
lat_odd_s = lat_odd_n - 90.0
|
||||
lat_even_s = lat_even_n - 90
|
||||
lat_odd_s = lat_odd_n - 90
|
||||
|
||||
# chose which solution corrispondes to receiver location
|
||||
lat_even = lat_even_n if lat_ref > 0 else lat_even_s
|
||||
@@ -73,41 +66,44 @@ def surface_position(msg0, msg1, t0, t1, lat_ref, lon_ref):
|
||||
return None
|
||||
|
||||
# compute ni, longitude index m, and longitude
|
||||
if t0 > t1:
|
||||
# (people pass int+int or datetime+datetime)
|
||||
if t0 > t1: # type: ignore
|
||||
lat = lat_even
|
||||
nl = common.cprNL(lat_even)
|
||||
ni = max(common.cprNL(lat_even) - 0, 1)
|
||||
m = common.floor(cprlon_even * (nl - 1) - cprlon_odd * nl + 0.5)
|
||||
lon = (90.0 / ni) * (m % ni + cprlon_even)
|
||||
lon = (90 / ni) * (m % ni + cprlon_even)
|
||||
else:
|
||||
lat = lat_odd
|
||||
nl = common.cprNL(lat_odd)
|
||||
ni = max(common.cprNL(lat_odd) - 1, 1)
|
||||
m = common.floor(cprlon_even * (nl - 1) - cprlon_odd * nl + 0.5)
|
||||
lon = (90.0 / ni) * (m % ni + cprlon_odd)
|
||||
lon = (90 / ni) * (m % ni + cprlon_odd)
|
||||
|
||||
# four possible longitude solutions
|
||||
lons = [lon, lon + 90.0, lon + 180.0, lon + 270.0]
|
||||
lons = [lon, lon + 90, lon + 180, lon + 270]
|
||||
|
||||
# make sure lons are between -180 and 180
|
||||
lons = [(l + 180) % 360 - 180 for l in lons]
|
||||
lons = [(lon + 180) % 360 - 180 for lon in lons]
|
||||
|
||||
# the closest solution to receiver is the correct one
|
||||
dls = [abs(lon_ref - l) for l in lons]
|
||||
dls = [abs(lon_ref - lon) for lon in lons]
|
||||
imin = min(range(4), key=dls.__getitem__)
|
||||
lon = lons[imin]
|
||||
|
||||
return round(lat, 5), round(lon, 5)
|
||||
return lat, lon
|
||||
|
||||
|
||||
def surface_position_with_ref(msg, lat_ref, lon_ref):
|
||||
def surface_position_with_ref(
|
||||
msg: str, lat_ref: float, lon_ref: float
|
||||
) -> tuple[float, float]:
|
||||
"""Decode surface position with only one message,
|
||||
knowing reference nearby location, such as previously calculated location,
|
||||
ground station, or airport location, etc. The reference position shall
|
||||
be with in 45NM of the true position.
|
||||
be within 45NM of the true position.
|
||||
|
||||
Args:
|
||||
msg (string): even message (28 bytes hexadecimal string)
|
||||
msg (str): even message (28 hexdigits)
|
||||
lat_ref: previous known latitude
|
||||
lon_ref: previous known longitude
|
||||
|
||||
@@ -117,11 +113,11 @@ def surface_position_with_ref(msg, lat_ref, lon_ref):
|
||||
|
||||
mb = common.hex2bin(msg)[32:]
|
||||
|
||||
cprlat = common.bin2int(mb[22:39]) / 131072.0
|
||||
cprlon = common.bin2int(mb[39:56]) / 131072.0
|
||||
cprlat = common.bin2int(mb[22:39]) / 131072
|
||||
cprlon = common.bin2int(mb[39:56]) / 131072
|
||||
|
||||
i = int(mb[21])
|
||||
d_lat = 90.0 / 59 if i else 90.0 / 60
|
||||
d_lat = 90 / 59 if i else 90 / 60
|
||||
|
||||
j = common.floor(lat_ref / d_lat) + common.floor(
|
||||
0.5 + ((lat_ref % d_lat) / d_lat) - cprlat
|
||||
@@ -132,9 +128,9 @@ def surface_position_with_ref(msg, lat_ref, lon_ref):
|
||||
ni = common.cprNL(lat) - i
|
||||
|
||||
if ni > 0:
|
||||
d_lon = 90.0 / ni
|
||||
d_lon = 90 / ni
|
||||
else:
|
||||
d_lon = 90.0
|
||||
d_lon = 90
|
||||
|
||||
m = common.floor(lon_ref / d_lon) + common.floor(
|
||||
0.5 + ((lon_ref % d_lon) / d_lon) - cprlon
|
||||
@@ -142,27 +138,32 @@ def surface_position_with_ref(msg, lat_ref, lon_ref):
|
||||
|
||||
lon = d_lon * (m + cprlon)
|
||||
|
||||
return round(lat, 5), round(lon, 5)
|
||||
return lat, lon
|
||||
|
||||
|
||||
def surface_velocity(msg, rtn_sources=False):
|
||||
"""Decode surface velocity from from a surface position message
|
||||
def surface_velocity(
|
||||
msg: str, source: bool = False
|
||||
) -> tuple[None | float, None | float, int, str]:
|
||||
"""Decode surface velocity from a surface position message
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
rtn_source (boolean): If the function will return
|
||||
the sources for direction of travel and vertical
|
||||
rate. This will change the return value from a four
|
||||
element array to a six element array.
|
||||
msg (str): 28 hexdigits string
|
||||
source (boolean): Include direction and vertical rate sources in return.
|
||||
Default to False.
|
||||
If set to True, the function will return six value instead of four.
|
||||
|
||||
Returns:
|
||||
(int, float, int, string, string, None): speed (kt),
|
||||
ground track (degree), None for rate of climb/descend (ft/min),
|
||||
and speed type ('GS' for ground speed), direction source
|
||||
('true_north' for ground track / true north as reference),
|
||||
None rate of climb/descent source.
|
||||
"""
|
||||
int, float, int, string, [string], [string]:
|
||||
- Speed (kt)
|
||||
- Angle (degree), ground track
|
||||
- Vertical rate, always 0
|
||||
- Speed type ('GS' for ground speed, 'AS' for airspeed)
|
||||
- [Optional] Direction source ('TRUE_NORTH')
|
||||
- [Optional] Vertical rate source (None)
|
||||
|
||||
if common.typecode(msg) < 5 or common.typecode(msg) > 8:
|
||||
"""
|
||||
tc = common.typecode(msg)
|
||||
if tc is None or tc < 5 or tc > 8:
|
||||
raise RuntimeError("%s: Not a surface message, expecting 5<TC<8" % msg)
|
||||
|
||||
mb = common.hex2bin(msg)[32:]
|
||||
@@ -170,29 +171,27 @@ def surface_velocity(msg, rtn_sources=False):
|
||||
# ground track
|
||||
trk_status = int(mb[12])
|
||||
if trk_status == 1:
|
||||
trk = common.bin2int(mb[13:20]) * 360.0 / 128.0
|
||||
trk = round(trk, 1)
|
||||
trk = common.bin2int(mb[13:20]) * 360 / 128
|
||||
else:
|
||||
trk = None
|
||||
|
||||
# ground movment / speed
|
||||
# ground movement / speed
|
||||
mov = common.bin2int(mb[5:12])
|
||||
|
||||
if mov == 0 or mov > 124:
|
||||
spd = None
|
||||
elif mov == 1:
|
||||
spd = 0
|
||||
spd = 0.0
|
||||
elif mov == 124:
|
||||
spd = 175
|
||||
spd = 175.0
|
||||
else:
|
||||
movs = [2, 9, 13, 39, 94, 109, 124]
|
||||
kts = [0.125, 1, 2, 15, 70, 100, 175]
|
||||
i = next(m[0] for m in enumerate(movs) if m[1] > mov)
|
||||
step = (kts[i] - kts[i - 1]) * 1.0 / (movs[i] - movs[i - 1])
|
||||
spd = kts[i - 1] + (mov - movs[i - 1]) * step
|
||||
spd = round(spd, 2)
|
||||
mov_lb = [2, 9, 13, 39, 94, 109, 124]
|
||||
kts_lb: list[float] = [0.125, 1, 2, 15, 70, 100, 175]
|
||||
step: list[float] = [0.125, 0.25, 0.5, 1, 2, 5]
|
||||
i = next(m[0] for m in enumerate(mov_lb) if m[1] > mov)
|
||||
spd = kts_lb[i - 1] + (mov - mov_lb[i - 1]) * step[i - 1]
|
||||
|
||||
if rtn_sources:
|
||||
return spd, trk, 0, "GS", "true_north", None
|
||||
if source:
|
||||
return spd, trk, 0, "GS", "TRUE_NORTH", None # type: ignore
|
||||
else:
|
||||
return spd, trk, 0, "GS"
|
||||
|
||||
@@ -1,40 +1,24 @@
|
||||
# Copyright (C) 2018 Junzi Sun (TU Delft)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
# ------------------------------------------
|
||||
# BDS 0,8
|
||||
# ADS-B TC=1-4
|
||||
# Aircraft identitification and category
|
||||
# Aircraft identification and category
|
||||
# ------------------------------------------
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
from pyModeS.decoder import common
|
||||
from ... import common
|
||||
|
||||
|
||||
def category(msg):
|
||||
def category(msg: str) -> int:
|
||||
"""Aircraft category number
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
int: category number
|
||||
"""
|
||||
|
||||
if common.typecode(msg) < 1 or common.typecode(msg) > 4:
|
||||
tc = common.typecode(msg)
|
||||
if tc is None or tc < 1 or tc > 4:
|
||||
raise RuntimeError("%s: Not a identification message" % msg)
|
||||
|
||||
msgbin = common.hex2bin(msg)
|
||||
@@ -42,17 +26,18 @@ def category(msg):
|
||||
return common.bin2int(mebin[5:8])
|
||||
|
||||
|
||||
def callsign(msg):
|
||||
def callsign(msg: str) -> str:
|
||||
"""Aircraft callsign
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
string: callsign
|
||||
"""
|
||||
tc = common.typecode(msg)
|
||||
|
||||
if common.typecode(msg) < 1 or common.typecode(msg) > 4:
|
||||
if tc is None or tc < 1 or tc > 4:
|
||||
raise RuntimeError("%s: Not a identification message" % msg)
|
||||
|
||||
chars = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ#####_###############0123456789######"
|
||||
|
||||
@@ -1,53 +1,41 @@
|
||||
# Copyright (C) 2018 Junzi Sun (TU Delft)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
# ------------------------------------------
|
||||
# BDS 0,9
|
||||
# ADS-B TC=19
|
||||
# Aircraft Airborn velocity
|
||||
# Aircraft Airborne velocity
|
||||
# ------------------------------------------
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
from pyModeS.decoder import common
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
|
||||
from ... import common
|
||||
|
||||
def airborne_velocity(msg, rtn_sources=False):
|
||||
"""Calculate the speed, track (or heading), and vertical rate
|
||||
|
||||
def airborne_velocity(
|
||||
msg: str, source: bool = False
|
||||
) -> None | tuple[None | int, None | float, None | int, str]:
|
||||
"""Decode airborne velocity.
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
rtn_source (boolean): If the function will return
|
||||
the sources for direction of travel and vertical
|
||||
rate. This will change the return value from a four
|
||||
element array to a six element array.
|
||||
msg (str): 28 hexdigits string
|
||||
source (boolean): Include direction and vertical rate sources in return.
|
||||
Default to False.
|
||||
If set to True, the function will return six value instead of four.
|
||||
|
||||
Returns:
|
||||
(int, float, int, string, string, string): speed (kt),
|
||||
ground track or heading (degree),
|
||||
rate of climb/descent (ft/min), speed type
|
||||
('GS' for ground speed, 'AS' for airspeed),
|
||||
direction source ('true_north' for ground track / true north
|
||||
as refrence, 'mag_north' for magnetic north as reference),
|
||||
rate of climb/descent source ('Baro' for barometer, 'GNSS'
|
||||
for GNSS constellation).
|
||||
"""
|
||||
int, float, int, string, [string], [string]:
|
||||
- Speed (kt)
|
||||
- Angle (degree), either ground track or heading
|
||||
- Vertical rate (ft/min)
|
||||
- Speed type ('GS' for ground speed, 'AS' for airspeed)
|
||||
- [Optional] Direction source ('TRUE_NORTH' or 'MAGNETIC_NORTH')
|
||||
- [Optional] Vertical rate source ('BARO' or 'GNSS')
|
||||
|
||||
"""
|
||||
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
|
||||
)
|
||||
|
||||
mb = common.hex2bin(msg)[32:]
|
||||
|
||||
@@ -56,77 +44,98 @@ def airborne_velocity(msg, rtn_sources=False):
|
||||
if common.bin2int(mb[14:24]) == 0 or common.bin2int(mb[25:35]) == 0:
|
||||
return None
|
||||
|
||||
trk_or_hdg: None | float
|
||||
spd: None | float
|
||||
|
||||
if subtype in (1, 2):
|
||||
v_ew_sign = -1 if mb[13] == "1" else 1
|
||||
v_ew = common.bin2int(mb[14:24]) - 1 # east-west velocity
|
||||
if subtype == 2: # Supersonic
|
||||
v_ew *= 4
|
||||
v_ew = common.bin2int(mb[14:24])
|
||||
v_ns = common.bin2int(mb[25:35])
|
||||
|
||||
v_ns_sign = -1 if mb[24] == "1" else 1
|
||||
v_ns = common.bin2int(mb[25:35]) - 1 # north-south velocity
|
||||
if subtype == 2: # Supersonic
|
||||
v_ns *= 4
|
||||
if v_ew == 0 or v_ns == 0:
|
||||
spd = None
|
||||
trk_or_hdg = None
|
||||
vs = None
|
||||
else:
|
||||
v_ew_sign = -1 if mb[13] == "1" else 1
|
||||
v_ew = v_ew - 1 # east-west velocity
|
||||
if subtype == 2: # Supersonic
|
||||
v_ew *= 4
|
||||
|
||||
v_we = v_ew_sign * v_ew
|
||||
v_sn = v_ns_sign * v_ns
|
||||
v_ns_sign = -1 if mb[24] == "1" else 1
|
||||
v_ns = v_ns - 1 # north-south velocity
|
||||
if subtype == 2: # Supersonic
|
||||
v_ns *= 4
|
||||
|
||||
spd = math.sqrt(v_sn * v_sn + v_we * v_we) # unit in kts
|
||||
spd = int(spd)
|
||||
v_we = v_ew_sign * v_ew
|
||||
v_sn = v_ns_sign * v_ns
|
||||
|
||||
trk = math.atan2(v_we, v_sn)
|
||||
trk = math.degrees(trk) # convert to degrees
|
||||
trk = trk if trk >= 0 else trk + 360 # no negative val
|
||||
spd = math.sqrt(v_sn * v_sn + v_we * v_we) # unit in kts
|
||||
spd = int(spd)
|
||||
|
||||
tag = "GS"
|
||||
trk_or_hdg = round(trk, 2)
|
||||
dir_type = "true_north"
|
||||
trk = math.atan2(v_we, v_sn)
|
||||
trk = math.degrees(trk) # convert to degrees
|
||||
trk = trk if trk >= 0 else trk + 360 # no negative val
|
||||
|
||||
trk_or_hdg = trk
|
||||
|
||||
spd_type = "GS"
|
||||
dir_type = "TRUE_NORTH"
|
||||
|
||||
else:
|
||||
if mb[13] == "0":
|
||||
hdg = None
|
||||
else:
|
||||
hdg = common.bin2int(mb[14:24]) / 1024.0 * 360.0
|
||||
hdg = round(hdg, 2)
|
||||
hdg = common.bin2int(mb[14:24]) / 1024 * 360.0
|
||||
|
||||
trk_or_hdg = hdg
|
||||
|
||||
spd = common.bin2int(mb[25:35])
|
||||
spd = None if spd == 0 else spd - 1
|
||||
if subtype == 4: # Supersonic
|
||||
if subtype == 4 and spd is not None: # Supersonic
|
||||
spd *= 4
|
||||
|
||||
if mb[24] == "0":
|
||||
tag = "IAS"
|
||||
spd_type = "IAS"
|
||||
else:
|
||||
tag = "TAS"
|
||||
spd_type = "TAS"
|
||||
|
||||
dir_type = "mag_north"
|
||||
dir_type = "MAGNETIC_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 = common.bin2int(mb[37:46])
|
||||
rocd = None if vr == 0 else int(vr_sign * (vr - 1) * 64)
|
||||
vs = None if vr == 0 else int(vr_sign * (vr - 1) * 64)
|
||||
|
||||
if rtn_sources:
|
||||
return spd, trk_or_hdg, rocd, tag, dir_type, vr_source
|
||||
if source:
|
||||
return ( # type: ignore
|
||||
spd,
|
||||
trk_or_hdg,
|
||||
vs,
|
||||
spd_type,
|
||||
dir_type,
|
||||
vr_source,
|
||||
)
|
||||
else:
|
||||
return spd, trk_or_hdg, rocd, tag
|
||||
return spd, trk_or_hdg, vs, spd_type
|
||||
|
||||
|
||||
def altitude_diff(msg):
|
||||
"""Decode the differece between GNSS and barometric altitude
|
||||
def altitude_diff(msg: str) -> None | float:
|
||||
"""Decode the differece between GNSS and barometric altitude.
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string, TC=19
|
||||
msg (str): 28 hexdigits string, TC=19
|
||||
|
||||
Returns:
|
||||
int: Altitude difference in ft. Negative value indicates GNSS altitude
|
||||
below barometric altitude.
|
||||
int: Altitude difference in feet. Negative value indicates GNSS altitude
|
||||
below barometric altitude.
|
||||
|
||||
"""
|
||||
tc = common.typecode(msg)
|
||||
|
||||
if tc != 19:
|
||||
raise RuntimeError("%s: Not a airborne velocity message, expecting TC=19" % msg)
|
||||
if tc is None or tc != 19:
|
||||
raise RuntimeError(
|
||||
"%s: Not a airborne velocity message, expecting TC=19" % msg
|
||||
)
|
||||
|
||||
msgbin = common.hex2bin(msg)
|
||||
sign = -1 if int(msgbin[80]) else 1
|
||||
|
||||
@@ -1,68 +1,53 @@
|
||||
# Copyright (C) 2018 Junzi Sun (TU Delft)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# ------------------------------------------
|
||||
# BDS 1,0
|
||||
# Data link capability report
|
||||
# ------------------------------------------
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros
|
||||
|
||||
from ... import common
|
||||
|
||||
|
||||
def is10(msg):
|
||||
def is10(msg: str) -> bool:
|
||||
"""Check if a message is likely to be BDS code 1,0
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
bool: True or False
|
||||
"""
|
||||
|
||||
if allzeros(msg):
|
||||
if common.allzeros(msg):
|
||||
return False
|
||||
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
|
||||
# first 8 bits must be 0x10
|
||||
if d[0:8] != "00010000":
|
||||
return False
|
||||
|
||||
# bit 10 to 14 are reserved
|
||||
if bin2int(d[9:14]) != 0:
|
||||
if common.bin2int(d[9:14]) != 0:
|
||||
return False
|
||||
|
||||
# overlay capabilty conflict
|
||||
if d[14] == "1" and bin2int(d[16:23]) < 5:
|
||||
# overlay capability conflict
|
||||
if d[14] == "1" and common.bin2int(d[16:23]) < 5:
|
||||
return False
|
||||
if d[14] == "0" and bin2int(d[16:23]) > 4:
|
||||
if d[14] == "0" and common.bin2int(d[16:23]) > 4:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def ovc10(msg):
|
||||
def ovc10(msg: str) -> int:
|
||||
"""Return the overlay control capability
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
int: Whether the transponder is OVC capable
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
|
||||
return int(d[14])
|
||||
|
||||
@@ -1,45 +1,29 @@
|
||||
# Copyright (C) 2018 Junzi Sun (TU Delft)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
# ------------------------------------------
|
||||
# BDS 1,7
|
||||
# Common usage GICB capability report
|
||||
# ------------------------------------------
|
||||
|
||||
from typing import List
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros
|
||||
from ... import common
|
||||
|
||||
|
||||
def is17(msg):
|
||||
def is17(msg: str) -> bool:
|
||||
"""Check if a message is likely to be BDS code 1,7
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
bool: True or False
|
||||
"""
|
||||
|
||||
if allzeros(msg):
|
||||
if common.allzeros(msg):
|
||||
return False
|
||||
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
|
||||
if bin2int(d[28:56]) != 0:
|
||||
if common.bin2int(d[24:56]) != 0:
|
||||
return False
|
||||
|
||||
caps = cap17(msg)
|
||||
@@ -56,14 +40,14 @@ def is17(msg):
|
||||
return True
|
||||
|
||||
|
||||
def cap17(msg):
|
||||
def cap17(msg: str) -> List[str]:
|
||||
"""Extract capacities from BDS 1,7 message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
list: list of suport BDS codes
|
||||
list: list of supported BDS codes
|
||||
"""
|
||||
allbds = [
|
||||
"05",
|
||||
@@ -90,14 +74,10 @@ def cap17(msg):
|
||||
"56",
|
||||
"5F",
|
||||
"60",
|
||||
"NA",
|
||||
"NA",
|
||||
"E1",
|
||||
"E2",
|
||||
]
|
||||
|
||||
d = hex2bin(data(msg))
|
||||
idx = [i for i, v in enumerate(d[:28]) if v == "1"]
|
||||
capacity = ["BDS" + allbds[i] for i in idx if allbds[i] is not "NA"]
|
||||
d = common.hex2bin(common.data(msg))
|
||||
idx = [i for i, v in enumerate(d[:24]) if v == "1"]
|
||||
capacity = ["BDS" + allbds[i] for i in idx]
|
||||
|
||||
return capacity
|
||||
|
||||
@@ -1,74 +1,60 @@
|
||||
# Copyright (C) 2018 Junzi Sun (TU Delft)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# ------------------------------------------
|
||||
# BDS 2,0
|
||||
# Aircraft identification
|
||||
# ------------------------------------------
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros
|
||||
from ... import common
|
||||
|
||||
|
||||
def is20(msg):
|
||||
def is20(msg: str) -> bool:
|
||||
"""Check if a message is likely to be BDS code 2,0
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
bool: True or False
|
||||
"""
|
||||
|
||||
if allzeros(msg):
|
||||
if common.allzeros(msg):
|
||||
return False
|
||||
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
|
||||
if d[0:8] != "00100000":
|
||||
return False
|
||||
|
||||
cs = cs20(msg)
|
||||
# allow empty callsign
|
||||
if common.bin2int(d[8:56]) == 0:
|
||||
return True
|
||||
|
||||
if "#" in cs:
|
||||
if "#" in cs20(msg):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def cs20(msg):
|
||||
def cs20(msg: str) -> str:
|
||||
"""Aircraft callsign
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS40) string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
string: callsign, max. 8 chars
|
||||
"""
|
||||
chars = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ#####_###############0123456789######"
|
||||
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
|
||||
cs = ""
|
||||
cs += chars[bin2int(d[8:14])]
|
||||
cs += chars[bin2int(d[14:20])]
|
||||
cs += chars[bin2int(d[20:26])]
|
||||
cs += chars[bin2int(d[26:32])]
|
||||
cs += chars[bin2int(d[32:38])]
|
||||
cs += chars[bin2int(d[38:44])]
|
||||
cs += chars[bin2int(d[44:50])]
|
||||
cs += chars[bin2int(d[50:56])]
|
||||
cs += chars[common.bin2int(d[8:14])]
|
||||
cs += chars[common.bin2int(d[14:20])]
|
||||
cs += chars[common.bin2int(d[20:26])]
|
||||
cs += chars[common.bin2int(d[26:32])]
|
||||
cs += chars[common.bin2int(d[32:38])]
|
||||
cs += chars[common.bin2int(d[38:44])]
|
||||
cs += chars[common.bin2int(d[44:50])]
|
||||
cs += chars[common.bin2int(d[50:56])]
|
||||
|
||||
return cs
|
||||
|
||||
@@ -1,41 +1,25 @@
|
||||
# Copyright (C) 2018 Junzi Sun (TU Delft)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# ------------------------------------------
|
||||
# BDS 3,0
|
||||
# ACAS active resolution advisory
|
||||
# ------------------------------------------
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros
|
||||
from ... import common
|
||||
|
||||
|
||||
def is30(msg):
|
||||
"""Check if a message is likely to be BDS code 2,0
|
||||
def is30(msg: str) -> bool:
|
||||
"""Check if a message is likely to be BDS code 3,0
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
bool: True or False
|
||||
"""
|
||||
|
||||
if allzeros(msg):
|
||||
if common.allzeros(msg):
|
||||
return False
|
||||
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
|
||||
if d[0:8] != "00110000":
|
||||
return False
|
||||
@@ -45,7 +29,7 @@ def is30(msg):
|
||||
return False
|
||||
|
||||
# reserved for ACAS III, in far future
|
||||
if bin2int(d[15:22]) >= 48:
|
||||
if common.bin2int(d[15:22]) >= 48:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
@@ -1,136 +1,124 @@
|
||||
# Copyright (C) 2018 Junzi Sun (TU Delft)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# ------------------------------------------
|
||||
# BDS 4,0
|
||||
# Selected vertical intention
|
||||
# ------------------------------------------
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
import warnings
|
||||
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros, wrongstatus
|
||||
from typing import Optional
|
||||
|
||||
from ... import common
|
||||
|
||||
|
||||
def is40(msg):
|
||||
def is40(msg: str) -> bool:
|
||||
"""Check if a message is likely to be BDS code 4,0
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
bool: True or False
|
||||
"""
|
||||
|
||||
if allzeros(msg):
|
||||
if common.allzeros(msg):
|
||||
return False
|
||||
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
|
||||
# status bit 1, 14, and 27
|
||||
|
||||
if wrongstatus(d, 1, 2, 13):
|
||||
if common.wrongstatus(d, 1, 2, 13):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 14, 15, 26):
|
||||
if common.wrongstatus(d, 14, 15, 26):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 27, 28, 39):
|
||||
if common.wrongstatus(d, 27, 28, 39):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 48, 49, 51):
|
||||
if common.wrongstatus(d, 48, 49, 51):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 54, 55, 56):
|
||||
if common.wrongstatus(d, 54, 55, 56):
|
||||
return False
|
||||
|
||||
# bits 40-47 and 52-53 shall all be zero
|
||||
|
||||
if bin2int(d[39:47]) != 0:
|
||||
if common.bin2int(d[39:47]) != 0:
|
||||
return False
|
||||
|
||||
if bin2int(d[51:53]) != 0:
|
||||
if common.bin2int(d[51:53]) != 0:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def selalt40mcp(msg):
|
||||
def selalt40mcp(msg: str) -> Optional[int]:
|
||||
"""Selected altitude, MCP/FCU
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS40) string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
int: altitude in feet
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
|
||||
if d[0] == "0":
|
||||
return None
|
||||
|
||||
alt = bin2int(d[1:13]) * 16 # ft
|
||||
alt = common.bin2int(d[1:13]) * 16 # ft
|
||||
return alt
|
||||
|
||||
|
||||
def selalt40fms(msg):
|
||||
def selalt40fms(msg: str) -> Optional[int]:
|
||||
"""Selected altitude, FMS
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS40) string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
int: altitude in feet
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
|
||||
if d[13] == "0":
|
||||
return None
|
||||
|
||||
alt = bin2int(d[14:26]) * 16 # ft
|
||||
alt = common.bin2int(d[14:26]) * 16 # ft
|
||||
return alt
|
||||
|
||||
|
||||
def p40baro(msg):
|
||||
def p40baro(msg: str) -> Optional[float]:
|
||||
"""Barometric pressure setting
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS40) string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
float: pressure in millibar
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
|
||||
if d[26] == "0":
|
||||
return None
|
||||
|
||||
p = bin2int(d[27:39]) * 0.1 + 800 # millibar
|
||||
p = common.bin2int(d[27:39]) * 0.1 + 800 # millibar
|
||||
return p
|
||||
|
||||
|
||||
def alt40mcp(msg):
|
||||
def alt40mcp(msg: str) -> Optional[int]:
|
||||
warnings.warn(
|
||||
"alt40mcp() has been renamed to selalt40mcp(). It will be removed in the future.",
|
||||
"""alt40mcp() has been renamed to selalt40mcp().
|
||||
It will be removed in the future.""",
|
||||
DeprecationWarning,
|
||||
)
|
||||
return selalt40mcp(msg)
|
||||
|
||||
|
||||
def alt40fms(msg):
|
||||
def alt40fms(msg: str) -> Optional[int]:
|
||||
warnings.warn(
|
||||
"alt40fms() has been renamed to selalt40fms(). It will be removed in the future.",
|
||||
"""alt40fms() has been renamed to selalt40fms().
|
||||
It will be removed in the future.""",
|
||||
DeprecationWarning,
|
||||
)
|
||||
return selalt40mcp(msg)
|
||||
return selalt40fms(msg)
|
||||
|
||||
@@ -1,59 +1,45 @@
|
||||
# Copyright (C) 2018 Junzi Sun (TU Delft)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# ------------------------------------------
|
||||
# BDS 4,4
|
||||
# Meteorological routine air report
|
||||
# ------------------------------------------
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros, wrongstatus
|
||||
from typing import Optional, Tuple
|
||||
|
||||
from ... import common
|
||||
|
||||
|
||||
def is44(msg):
|
||||
def is44(msg: str) -> bool:
|
||||
"""Check if a message is likely to be BDS code 4,4.
|
||||
|
||||
Meteorological routine air report
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
bool: True or False
|
||||
|
||||
"""
|
||||
if allzeros(msg):
|
||||
if common.allzeros(msg):
|
||||
return False
|
||||
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
|
||||
# status bit 5, 35, 47, 50
|
||||
if wrongstatus(d, 5, 6, 23):
|
||||
if common.wrongstatus(d, 5, 6, 23):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 35, 36, 46):
|
||||
if common.wrongstatus(d, 35, 36, 46):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 47, 48, 49):
|
||||
if common.wrongstatus(d, 47, 48, 49):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 50, 51, 56):
|
||||
if common.wrongstatus(d, 50, 51, 56):
|
||||
return False
|
||||
|
||||
# Bits 1-4 indicate source, values > 4 reserved and should not occur
|
||||
if bin2int(d[0:4]) > 4:
|
||||
if common.bin2int(d[0:4]) > 4:
|
||||
return False
|
||||
|
||||
vw, dw = wind44(msg)
|
||||
@@ -67,111 +53,109 @@ def is44(msg):
|
||||
return True
|
||||
|
||||
|
||||
def wind44(msg):
|
||||
def wind44(msg: str) -> Tuple[Optional[int], Optional[float]]:
|
||||
"""Wind speed and direction.
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
(int, float): speed (kt), direction (degree)
|
||||
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
|
||||
status = int(d[4])
|
||||
if not status:
|
||||
return None, None
|
||||
|
||||
speed = bin2int(d[5:14]) # knots
|
||||
direction = bin2int(d[14:23]) * 180.0 / 256.0 # degree
|
||||
speed = common.bin2int(d[5:14]) # knots
|
||||
direction = common.bin2int(d[14:23]) * 180 / 256 # degree
|
||||
|
||||
return round(speed, 0), round(direction, 1)
|
||||
return speed, direction
|
||||
|
||||
|
||||
def temp44(msg):
|
||||
def temp44(msg: str) -> Tuple[float, float]:
|
||||
"""Static air temperature.
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
float, float: temperature and alternative temperature in Celsius degree.
|
||||
Note: Two values returns due to what seems to be an inconsistancy
|
||||
Note: Two values returns due to what seems to be an inconsistency
|
||||
error in ICAO 9871 (2008) Appendix A-67.
|
||||
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
|
||||
sign = int(d[23])
|
||||
value = bin2int(d[24:34])
|
||||
value = common.bin2int(d[24:34])
|
||||
|
||||
if sign:
|
||||
value = value - 1024
|
||||
|
||||
temp = value * 0.25 # celsius
|
||||
temp = round(temp, 2)
|
||||
|
||||
temp_alternative = value * 0.125 # celsius
|
||||
temp_alternative = round(temp_alternative, 3)
|
||||
|
||||
return temp, temp_alternative
|
||||
|
||||
|
||||
def p44(msg):
|
||||
def p44(msg: str) -> Optional[int]:
|
||||
"""Static pressure.
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
int: static pressure in hPa
|
||||
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
|
||||
if d[34] == "0":
|
||||
return None
|
||||
|
||||
p = bin2int(d[35:46]) # hPa
|
||||
p = common.bin2int(d[35:46]) # hPa
|
||||
|
||||
return p
|
||||
|
||||
|
||||
def hum44(msg):
|
||||
def hum44(msg: str) -> Optional[float]:
|
||||
"""humidity
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
float: percentage of humidity, [0 - 100] %
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
|
||||
if d[49] == "0":
|
||||
return None
|
||||
|
||||
hm = bin2int(d[50:56]) * 100.0 / 64 # %
|
||||
hm = common.bin2int(d[50:56]) * 100 / 64 # %
|
||||
|
||||
return round(hm, 1)
|
||||
return hm
|
||||
|
||||
|
||||
def turb44(msg):
|
||||
"""Turblence.
|
||||
def turb44(msg: str) -> Optional[int]:
|
||||
"""Turbulence.
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
int: turbulence level. 0=NIL, 1=Light, 2=Moderate, 3=Severe
|
||||
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
|
||||
if d[46] == "0":
|
||||
return None
|
||||
|
||||
turb = bin2int(d[47:49])
|
||||
turb = common.bin2int(d[47:49])
|
||||
|
||||
return turb
|
||||
|
||||
@@ -1,71 +1,57 @@
|
||||
# Copyright (C) 2018 Junzi Sun (TU Delft)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# ------------------------------------------
|
||||
# BDS 4,5
|
||||
# Meteorological hazard report
|
||||
# ------------------------------------------
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros, wrongstatus
|
||||
from typing import Optional
|
||||
|
||||
from ... import common
|
||||
|
||||
|
||||
def is45(msg):
|
||||
def is45(msg: str) -> bool:
|
||||
"""Check if a message is likely to be BDS code 4,5.
|
||||
|
||||
Meteorological hazard report
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
bool: True or False
|
||||
|
||||
"""
|
||||
if allzeros(msg):
|
||||
if common.allzeros(msg):
|
||||
return False
|
||||
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
|
||||
# status bit 1, 4, 7, 10, 13, 16, 27, 39
|
||||
if wrongstatus(d, 1, 2, 3):
|
||||
if common.wrongstatus(d, 1, 2, 3):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 4, 5, 6):
|
||||
if common.wrongstatus(d, 4, 5, 6):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 7, 8, 9):
|
||||
if common.wrongstatus(d, 7, 8, 9):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 10, 11, 12):
|
||||
if common.wrongstatus(d, 10, 11, 12):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 13, 14, 15):
|
||||
if common.wrongstatus(d, 13, 14, 15):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 16, 17, 26):
|
||||
if common.wrongstatus(d, 16, 17, 26):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 27, 28, 38):
|
||||
if common.wrongstatus(d, 27, 28, 38):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 39, 40, 51):
|
||||
if common.wrongstatus(d, 39, 40, 51):
|
||||
return False
|
||||
|
||||
# reserved
|
||||
if bin2int(d[51:56]) != 0:
|
||||
if common.bin2int(d[51:56]) != 0:
|
||||
return False
|
||||
|
||||
temp = temp45(msg)
|
||||
@@ -76,149 +62,148 @@ def is45(msg):
|
||||
return True
|
||||
|
||||
|
||||
def turb45(msg):
|
||||
def turb45(msg: str) -> Optional[int]:
|
||||
"""Turbulence.
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
int: Turbulence level. 0=NIL, 1=Light, 2=Moderate, 3=Severe
|
||||
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
if d[0] == "0":
|
||||
return None
|
||||
|
||||
turb = bin2int(d[1:3])
|
||||
turb = common.bin2int(d[1:3])
|
||||
return turb
|
||||
|
||||
|
||||
def ws45(msg):
|
||||
def ws45(msg: str) -> Optional[int]:
|
||||
"""Wind shear.
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
int: Wind shear level. 0=NIL, 1=Light, 2=Moderate, 3=Severe
|
||||
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
if d[3] == "0":
|
||||
return None
|
||||
|
||||
ws = bin2int(d[4:6])
|
||||
ws = common.bin2int(d[4:6])
|
||||
return ws
|
||||
|
||||
|
||||
def mb45(msg):
|
||||
def mb45(msg: str) -> Optional[int]:
|
||||
"""Microburst.
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
int: Microburst level. 0=NIL, 1=Light, 2=Moderate, 3=Severe
|
||||
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
if d[6] == "0":
|
||||
return None
|
||||
|
||||
mb = bin2int(d[7:9])
|
||||
mb = common.bin2int(d[7:9])
|
||||
return mb
|
||||
|
||||
|
||||
def ic45(msg):
|
||||
def ic45(msg: str) -> Optional[int]:
|
||||
"""Icing.
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
int: Icing level. 0=NIL, 1=Light, 2=Moderate, 3=Severe
|
||||
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
if d[9] == "0":
|
||||
return None
|
||||
|
||||
ic = bin2int(d[10:12])
|
||||
ic = common.bin2int(d[10:12])
|
||||
return ic
|
||||
|
||||
|
||||
def wv45(msg):
|
||||
def wv45(msg: str) -> Optional[int]:
|
||||
"""Wake vortex.
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
int: Wake vortex level. 0=NIL, 1=Light, 2=Moderate, 3=Severe
|
||||
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
if d[12] == "0":
|
||||
return None
|
||||
|
||||
ws = bin2int(d[13:15])
|
||||
ws = common.bin2int(d[13:15])
|
||||
return ws
|
||||
|
||||
|
||||
def temp45(msg):
|
||||
def temp45(msg: str) -> Optional[float]:
|
||||
"""Static air temperature.
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
float: tmeperature in Celsius degree
|
||||
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
|
||||
sign = int(d[16])
|
||||
value = bin2int(d[17:26])
|
||||
value = common.bin2int(d[17:26])
|
||||
|
||||
if sign:
|
||||
value = value - 512
|
||||
|
||||
temp = value * 0.25 # celsius
|
||||
temp = round(temp, 1)
|
||||
|
||||
return temp
|
||||
|
||||
|
||||
def p45(msg):
|
||||
def p45(msg: str) -> Optional[int]:
|
||||
"""Average static pressure.
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
int: static pressure in hPa
|
||||
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
if d[26] == "0":
|
||||
return None
|
||||
p = bin2int(d[27:38]) # hPa
|
||||
p = common.bin2int(d[27:38]) # hPa
|
||||
return p
|
||||
|
||||
|
||||
def rh45(msg):
|
||||
def rh45(msg: str) -> Optional[int]:
|
||||
"""Radio height.
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
int: radio height in ft
|
||||
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
if d[38] == "0":
|
||||
return None
|
||||
rh = bin2int(d[39:51]) * 16
|
||||
rh = common.bin2int(d[39:51]) * 16
|
||||
return rh
|
||||
|
||||
@@ -1,62 +1,48 @@
|
||||
# Copyright (C) 2018 Junzi Sun (TU Delft)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# ------------------------------------------
|
||||
# BDS 5,0
|
||||
# Track and turn report
|
||||
# ------------------------------------------
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros, wrongstatus
|
||||
from typing import Optional
|
||||
|
||||
from ... import common
|
||||
|
||||
|
||||
def is50(msg):
|
||||
def is50(msg: str) -> bool:
|
||||
"""Check if a message is likely to be BDS code 5,0
|
||||
(Track and turn report)
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
bool: True or False
|
||||
"""
|
||||
|
||||
if allzeros(msg):
|
||||
if common.allzeros(msg):
|
||||
return False
|
||||
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
|
||||
# status bit 1, 12, 24, 35, 46
|
||||
|
||||
if wrongstatus(d, 1, 3, 11):
|
||||
if common.wrongstatus(d, 1, 3, 11):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 12, 13, 23):
|
||||
if common.wrongstatus(d, 12, 13, 23):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 24, 25, 34):
|
||||
if common.wrongstatus(d, 24, 25, 34):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 35, 36, 45):
|
||||
if common.wrongstatus(d, 35, 36, 45):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 46, 47, 56):
|
||||
if common.wrongstatus(d, 46, 47, 56):
|
||||
return False
|
||||
|
||||
roll = roll50(msg)
|
||||
if (roll is not None) and abs(roll) > 60:
|
||||
if (roll is not None) and abs(roll) > 50:
|
||||
return False
|
||||
|
||||
gs = gs50(msg)
|
||||
@@ -73,88 +59,88 @@ def is50(msg):
|
||||
return True
|
||||
|
||||
|
||||
def roll50(msg):
|
||||
def roll50(msg: str) -> Optional[float]:
|
||||
"""Roll angle, BDS 5,0 message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS50) string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
float: angle in degrees,
|
||||
negative->left wing down, positive->right wing down
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
|
||||
if d[0] == "0":
|
||||
return None
|
||||
|
||||
sign = int(d[1]) # 1 -> left wing down
|
||||
value = bin2int(d[2:11])
|
||||
value = common.bin2int(d[2:11])
|
||||
|
||||
if sign:
|
||||
value = value - 512
|
||||
|
||||
angle = value * 45.0 / 256.0 # degree
|
||||
return round(angle, 1)
|
||||
angle = value * 45 / 256 # degree
|
||||
return angle
|
||||
|
||||
|
||||
def trk50(msg):
|
||||
def trk50(msg: str) -> Optional[float]:
|
||||
"""True track angle, BDS 5,0 message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS50) string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
float: angle in degrees to true north (from 0 to 360)
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
|
||||
if d[11] == "0":
|
||||
return None
|
||||
|
||||
sign = int(d[12]) # 1 -> west
|
||||
value = bin2int(d[13:23])
|
||||
value = common.bin2int(d[13:23])
|
||||
|
||||
if sign:
|
||||
value = value - 1024
|
||||
|
||||
trk = value * 90.0 / 512.0
|
||||
trk = value * 90 / 512.0
|
||||
|
||||
# convert from [-180, 180] to [0, 360]
|
||||
if trk < 0:
|
||||
trk = 360 + trk
|
||||
|
||||
return round(trk, 3)
|
||||
return trk
|
||||
|
||||
|
||||
def gs50(msg):
|
||||
def gs50(msg: str) -> Optional[float]:
|
||||
"""Ground speed, BDS 5,0 message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS50) string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
int: ground speed in knots
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
|
||||
if d[23] == "0":
|
||||
return None
|
||||
|
||||
spd = bin2int(d[24:34]) * 2 # kts
|
||||
spd = common.bin2int(d[24:34]) * 2 # kts
|
||||
return spd
|
||||
|
||||
|
||||
def rtrk50(msg):
|
||||
def rtrk50(msg: str) -> Optional[float]:
|
||||
"""Track angle rate, BDS 5,0 message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS50) string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
float: angle rate in degrees/second
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
|
||||
if d[34] == "0":
|
||||
return None
|
||||
@@ -163,27 +149,27 @@ def rtrk50(msg):
|
||||
return None
|
||||
|
||||
sign = int(d[35]) # 1 -> negative value, two's complement
|
||||
value = bin2int(d[36:45])
|
||||
value = common.bin2int(d[36:45])
|
||||
if sign:
|
||||
value = value - 512
|
||||
|
||||
angle = value * 8.0 / 256.0 # degree / sec
|
||||
return round(angle, 3)
|
||||
angle = value * 8 / 256 # degree / sec
|
||||
return angle
|
||||
|
||||
|
||||
def tas50(msg):
|
||||
def tas50(msg: str) -> Optional[float]:
|
||||
"""Aircraft true airspeed, BDS 5,0 message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS50) string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
int: true airspeed in knots
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
|
||||
if d[45] == "0":
|
||||
return None
|
||||
|
||||
tas = bin2int(d[46:56]) * 2 # kts
|
||||
tas = common.bin2int(d[46:56]) * 2 # kts
|
||||
return tas
|
||||
|
||||
@@ -1,58 +1,44 @@
|
||||
# Copyright (C) 2018 Junzi Sun (TU Delft)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# ------------------------------------------
|
||||
# BDS 5,3
|
||||
# Air-referenced state vector
|
||||
# ------------------------------------------
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros, wrongstatus
|
||||
from typing import Optional
|
||||
|
||||
from ... import common
|
||||
|
||||
|
||||
def is53(msg):
|
||||
def is53(msg: str) -> bool:
|
||||
"""Check if a message is likely to be BDS code 5,3
|
||||
(Air-referenced state vector)
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
bool: True or False
|
||||
"""
|
||||
|
||||
if allzeros(msg):
|
||||
if common.allzeros(msg):
|
||||
return False
|
||||
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
|
||||
# status bit 1, 13, 24, 34, 47
|
||||
|
||||
if wrongstatus(d, 1, 3, 12):
|
||||
if common.wrongstatus(d, 1, 3, 12):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 13, 14, 23):
|
||||
if common.wrongstatus(d, 13, 14, 23):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 24, 25, 33):
|
||||
if common.wrongstatus(d, 24, 25, 33):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 34, 35, 46):
|
||||
if common.wrongstatus(d, 34, 35, 46):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 47, 49, 56):
|
||||
if common.wrongstatus(d, 47, 49, 56):
|
||||
return False
|
||||
|
||||
ias = ias53(msg)
|
||||
@@ -74,105 +60,105 @@ def is53(msg):
|
||||
return True
|
||||
|
||||
|
||||
def hdg53(msg):
|
||||
def hdg53(msg: str) -> Optional[float]:
|
||||
"""Magnetic heading, BDS 5,3 message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS53) string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
float: angle in degrees to true north (from 0 to 360)
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
|
||||
if d[0] == "0":
|
||||
return None
|
||||
|
||||
sign = int(d[1]) # 1 -> west
|
||||
value = bin2int(d[2:12])
|
||||
value = common.bin2int(d[2:12])
|
||||
|
||||
if sign:
|
||||
value = value - 1024
|
||||
|
||||
hdg = value * 90.0 / 512.0 # degree
|
||||
hdg = value * 90 / 512 # degree
|
||||
|
||||
# convert from [-180, 180] to [0, 360]
|
||||
if hdg < 0:
|
||||
hdg = 360 + hdg
|
||||
|
||||
return round(hdg, 3)
|
||||
return hdg
|
||||
|
||||
|
||||
def ias53(msg):
|
||||
def ias53(msg: str) -> Optional[float]:
|
||||
"""Indicated airspeed, DBS 5,3 message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message
|
||||
msg (str): 28 hexdigits
|
||||
|
||||
Returns:
|
||||
int: indicated arispeed in knots
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
|
||||
if d[12] == "0":
|
||||
return None
|
||||
|
||||
ias = bin2int(d[13:23]) # knots
|
||||
ias = common.bin2int(d[13:23]) # knots
|
||||
return ias
|
||||
|
||||
|
||||
def mach53(msg):
|
||||
def mach53(msg: str) -> Optional[float]:
|
||||
"""MACH number, DBS 5,3 message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message
|
||||
msg (str): 28 hexdigits
|
||||
|
||||
Returns:
|
||||
float: MACH number
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
|
||||
if d[23] == "0":
|
||||
return None
|
||||
|
||||
mach = bin2int(d[24:33]) * 0.008
|
||||
return round(mach, 3)
|
||||
mach = common.bin2int(d[24:33]) * 0.008
|
||||
return mach
|
||||
|
||||
|
||||
def tas53(msg):
|
||||
def tas53(msg: str) -> Optional[float]:
|
||||
"""Aircraft true airspeed, BDS 5,3 message
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message
|
||||
msg (str): 28 hexdigits
|
||||
|
||||
Returns:
|
||||
float: true airspeed in knots
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
|
||||
if d[33] == "0":
|
||||
return None
|
||||
|
||||
tas = bin2int(d[34:46]) * 0.5 # kts
|
||||
return round(tas, 1)
|
||||
tas = common.bin2int(d[34:46]) * 0.5 # kts
|
||||
return tas
|
||||
|
||||
|
||||
def vr53(msg):
|
||||
def vr53(msg: str) -> Optional[int]:
|
||||
"""Vertical rate
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS60) string
|
||||
msg (str): 28 hexdigits (BDS60) string
|
||||
|
||||
Returns:
|
||||
int: vertical rate in feet/minutes
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
|
||||
if d[46] == "0":
|
||||
return None
|
||||
|
||||
sign = int(d[47]) # 1 -> negative value, two's complement
|
||||
value = bin2int(d[48:56])
|
||||
value = common.bin2int(d[48:56])
|
||||
|
||||
if value == 0 or value == 255: # all zeros or all ones
|
||||
return 0
|
||||
|
||||
@@ -1,57 +1,44 @@
|
||||
# Copyright (C) 2018 Junzi Sun (TU Delft)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# ------------------------------------------
|
||||
# BDS 6,0
|
||||
# Heading and speed report
|
||||
# ------------------------------------------
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros, wrongstatus
|
||||
from typing import Optional
|
||||
|
||||
from ... import common
|
||||
from ...extra import aero
|
||||
|
||||
|
||||
def is60(msg):
|
||||
def is60(msg: str) -> bool:
|
||||
"""Check if a message is likely to be BDS code 6,0
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
bool: True or False
|
||||
"""
|
||||
|
||||
if allzeros(msg):
|
||||
if common.allzeros(msg):
|
||||
return False
|
||||
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
|
||||
# status bit 1, 13, 24, 35, 46
|
||||
|
||||
if wrongstatus(d, 1, 2, 12):
|
||||
if common.wrongstatus(d, 1, 2, 12):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 13, 14, 23):
|
||||
if common.wrongstatus(d, 13, 14, 23):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 24, 25, 34):
|
||||
if common.wrongstatus(d, 24, 25, 34):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 35, 36, 45):
|
||||
if common.wrongstatus(d, 35, 36, 45):
|
||||
return False
|
||||
|
||||
if wrongstatus(d, 46, 47, 56):
|
||||
if common.wrongstatus(d, 46, 47, 56):
|
||||
return False
|
||||
|
||||
ias = ias60(msg)
|
||||
@@ -70,90 +57,98 @@ def is60(msg):
|
||||
if vr_ins is not None and abs(vr_ins) > 6000:
|
||||
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
|
||||
|
||||
|
||||
def hdg60(msg):
|
||||
def hdg60(msg: str) -> Optional[float]:
|
||||
"""Megnetic heading of aircraft
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS60) string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
float: heading in degrees to megnetic north (from 0 to 360)
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
|
||||
if d[0] == "0":
|
||||
return None
|
||||
|
||||
sign = int(d[1]) # 1 -> west
|
||||
value = bin2int(d[2:12])
|
||||
value = common.bin2int(d[2:12])
|
||||
|
||||
if sign:
|
||||
value = value - 1024
|
||||
|
||||
hdg = value * 90 / 512.0 # degree
|
||||
hdg = value * 90 / 512 # degree
|
||||
|
||||
# convert from [-180, 180] to [0, 360]
|
||||
if hdg < 0:
|
||||
hdg = 360 + hdg
|
||||
|
||||
return round(hdg, 3)
|
||||
return hdg
|
||||
|
||||
|
||||
def ias60(msg):
|
||||
def ias60(msg: str) -> Optional[float]:
|
||||
"""Indicated airspeed
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS60) string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
int: indicated airspeed in knots
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
|
||||
if d[12] == "0":
|
||||
return None
|
||||
|
||||
ias = bin2int(d[13:23]) # kts
|
||||
ias = common.bin2int(d[13:23]) # kts
|
||||
return ias
|
||||
|
||||
|
||||
def mach60(msg):
|
||||
def mach60(msg: str) -> Optional[float]:
|
||||
"""Aircraft MACH number
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS60) string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
float: MACH number
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
|
||||
if d[23] == "0":
|
||||
return None
|
||||
|
||||
mach = bin2int(d[24:34]) * 2.048 / 512.0
|
||||
return round(mach, 3)
|
||||
mach = common.bin2int(d[24:34]) * 2.048 / 512.0
|
||||
return mach
|
||||
|
||||
|
||||
def vr60baro(msg):
|
||||
def vr60baro(msg: str) -> Optional[int]:
|
||||
"""Vertical rate from barometric measurement, this value may be very noisy.
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS60) string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
int: vertical rate in feet/minutes
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
|
||||
if d[34] == "0":
|
||||
return None
|
||||
|
||||
sign = int(d[35]) # 1 -> negative value, two's complement
|
||||
value = bin2int(d[36:45])
|
||||
value = common.bin2int(d[36:45])
|
||||
|
||||
if value == 0 or value == 511: # all zeros or all ones
|
||||
return 0
|
||||
@@ -164,22 +159,22 @@ def vr60baro(msg):
|
||||
return roc
|
||||
|
||||
|
||||
def vr60ins(msg):
|
||||
"""Vertical rate messured by onbard equiments (IRS, AHRS)
|
||||
def vr60ins(msg: str) -> Optional[int]:
|
||||
"""Vertical rate measurd by onbard equiments (IRS, AHRS)
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message (BDS60) string
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
int: vertical rate in feet/minutes
|
||||
"""
|
||||
d = hex2bin(data(msg))
|
||||
d = common.hex2bin(common.data(msg))
|
||||
|
||||
if d[45] == "0":
|
||||
return None
|
||||
|
||||
sign = int(d[46]) # 1 -> negative value, two's complement
|
||||
value = bin2int(d[47:56])
|
||||
value = common.bin2int(d[47:56])
|
||||
|
||||
if value == 0 or value == 511: # all zeros or all ones
|
||||
return 0
|
||||
|
||||
87
pyModeS/decoder/bds/bds61.py
Normal file
87
pyModeS/decoder/bds/bds61.py
Normal file
@@ -0,0 +1,87 @@
|
||||
# ------------------------------------------
|
||||
# BDS 6,1
|
||||
# ADS-B TC=28
|
||||
# Aircraft Airborne status
|
||||
# ------------------------------------------
|
||||
|
||||
from ... 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:56]
|
||||
|
||||
squawk = common.squawk(idcode)
|
||||
return squawk
|
||||
552
pyModeS/decoder/bds/bds62.py
Normal file
552
pyModeS/decoder/bds/bds62.py
Normal file
@@ -0,0 +1,552 @@
|
||||
# ------------------------------------------
|
||||
# BDS 6,2
|
||||
# ADS-B TC=29
|
||||
# Target State and Status
|
||||
# ------------------------------------------
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from ... import common
|
||||
|
||||
|
||||
def selected_altitude(msg: str) -> tuple[None | float, str]:
|
||||
"""Decode selected altitude.
|
||||
|
||||
Args:
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
int: Selected altitude (ft)
|
||||
string: Source ('MCP/FCU' or 'FMS')
|
||||
|
||||
"""
|
||||
|
||||
if common.typecode(msg) != 29:
|
||||
raise RuntimeError(
|
||||
"%s: Not a target state and status message, expecting TC=29" % msg
|
||||
)
|
||||
|
||||
mb = common.hex2bin(msg)[32:]
|
||||
|
||||
subtype = common.bin2int(mb[5:7])
|
||||
|
||||
if subtype == 0:
|
||||
raise RuntimeError(
|
||||
"%s: ADS-B version 1 target state and status message does not"
|
||||
" contain selected altitude, use target altitude instead" % msg
|
||||
)
|
||||
|
||||
alt = common.bin2int(mb[9:20])
|
||||
if alt == 0:
|
||||
return None, "N/A"
|
||||
alt = (alt - 1) * 32
|
||||
alt_source = "MCP/FCU" if int(mb[8]) == 0 else "FMS"
|
||||
|
||||
return alt, alt_source
|
||||
|
||||
|
||||
def target_altitude(msg: str) -> tuple[None | int, str, str]:
|
||||
"""Decode target altitude.
|
||||
|
||||
Args:
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
int: Target altitude (ft)
|
||||
string: Source ('MCP/FCU', 'Holding mode' or 'FMS/RNAV')
|
||||
string: Altitude reference, either pressure altitude or barometric
|
||||
corrected altitude ('FL' or 'MSL')
|
||||
|
||||
"""
|
||||
|
||||
if common.typecode(msg) != 29:
|
||||
raise RuntimeError(
|
||||
"%s: Not a target state and status message, expecting TC=29" % msg
|
||||
)
|
||||
|
||||
mb = common.hex2bin(msg)[32:]
|
||||
|
||||
subtype = common.bin2int(mb[5:7])
|
||||
|
||||
if subtype == 1:
|
||||
raise RuntimeError(
|
||||
"%s: ADS-B version 2 target state and status message does not"
|
||||
" contain target altitude, use selected altitude instead" % msg
|
||||
)
|
||||
|
||||
alt_avail = common.bin2int(mb[7:9])
|
||||
if alt_avail == 0:
|
||||
return None, "N/A", ""
|
||||
elif alt_avail == 1:
|
||||
alt_source = "MCP/FCU"
|
||||
elif alt_avail == 2:
|
||||
alt_source = "Holding mode"
|
||||
else:
|
||||
alt_source = "FMS/RNAV"
|
||||
|
||||
alt_ref = "FL" if int(mb[9]) == 0 else "MSL"
|
||||
|
||||
alt = -1000 + common.bin2int(mb[15:25]) * 100
|
||||
|
||||
return alt, alt_source, alt_ref
|
||||
|
||||
|
||||
def vertical_mode(msg: str) -> None | int:
|
||||
"""Decode vertical mode.
|
||||
|
||||
Value Meaning
|
||||
----- -----------------------
|
||||
1 "Acquiring" mode
|
||||
2 "Capturing" or "Maintaining" mode
|
||||
3 Reserved
|
||||
|
||||
Args:
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
int: Vertical mode
|
||||
|
||||
"""
|
||||
|
||||
if common.typecode(msg) != 29:
|
||||
raise RuntimeError(
|
||||
"%s: Not a target state and status message, expecting TC=29" % msg
|
||||
)
|
||||
|
||||
mb = common.hex2bin(msg)[32:]
|
||||
|
||||
subtype = common.bin2int(mb[5:7])
|
||||
|
||||
if subtype == 1:
|
||||
raise RuntimeError(
|
||||
"%s: ADS-B version 2 target state and status message does not"
|
||||
" contain vertical mode, use vnav mode instead" % msg
|
||||
)
|
||||
|
||||
vertical_mode = common.bin2int(mb[13:15])
|
||||
if vertical_mode == 0:
|
||||
return None
|
||||
|
||||
return vertical_mode
|
||||
|
||||
|
||||
def horizontal_mode(msg: str) -> None | int:
|
||||
"""Decode horizontal mode.
|
||||
|
||||
Value Meaning
|
||||
----- -----------------------
|
||||
1 "Acquiring" mode
|
||||
2 "Capturing" or "Maintaining" mode
|
||||
3 Reserved
|
||||
|
||||
Args:
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
int: Horizontal mode
|
||||
|
||||
"""
|
||||
|
||||
if common.typecode(msg) != 29:
|
||||
raise RuntimeError(
|
||||
"%s: Not a target state and status message, expecting TC=29" % msg
|
||||
)
|
||||
|
||||
mb = common.hex2bin(msg)[32:]
|
||||
|
||||
subtype = common.bin2int(mb[5:7])
|
||||
|
||||
if subtype == 1:
|
||||
raise RuntimeError(
|
||||
"%s: ADS-B version 2 target state and status message does not "
|
||||
"contain horizontal mode, use lnav mode instead" % msg
|
||||
)
|
||||
|
||||
horizontal_mode = common.bin2int(mb[25:27])
|
||||
if horizontal_mode == 0:
|
||||
return None
|
||||
|
||||
return horizontal_mode
|
||||
|
||||
|
||||
def selected_heading(msg: str) -> None | float:
|
||||
"""Decode selected heading.
|
||||
|
||||
Args:
|
||||
msg (str): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
float: Selected heading (degree)
|
||||
|
||||
"""
|
||||
|
||||
if common.typecode(msg) != 29:
|
||||
raise RuntimeError(
|
||||
"%s: Not a target state and status message, expecting TC=29" % msg
|
||||
)
|
||||
|
||||
mb = common.hex2bin(msg)[32:]
|
||||
|
||||
subtype = common.bin2int(mb[5:7])
|
||||
|
||||
if subtype == 0:
|
||||
raise RuntimeError(
|
||||
"%s: ADS-B version 1 target state and status message does not "
|
||||
"contain selected heading, use target angle instead" % msg
|
||||
)
|
||||
|
||||
if int(mb[29]) == 0:
|
||||
return None
|
||||
else:
|
||||
hdg_sign = int(mb[30])
|
||||
hdg = (hdg_sign + 1) * common.bin2int(mb[31:39]) * (180 / 256)
|
||||
|
||||
return hdg
|
||||
|
||||
|
||||
def target_angle(msg: str) -> tuple[None | int, str, str]:
|
||||
"""Decode target heading/track angle.
|
||||
|
||||
Args:
|
||||
msg (str): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
int: Target angle (degree)
|
||||
string: Angle type ('Heading' or 'Track')
|
||||
string: Source ('MCP/FCU', 'Autopilot Mode' or 'FMS/RNAV')
|
||||
|
||||
"""
|
||||
|
||||
if common.typecode(msg) != 29:
|
||||
raise RuntimeError(
|
||||
"%s: Not a target state and status message, expecting TC=29" % msg
|
||||
)
|
||||
|
||||
mb = common.hex2bin(msg)[32:]
|
||||
|
||||
subtype = common.bin2int(mb[5:7])
|
||||
|
||||
if subtype == 1:
|
||||
raise RuntimeError(
|
||||
"%s: ADS-B version 2 target state and status message does not "
|
||||
"contain target angle, use selected heading instead" % msg
|
||||
)
|
||||
|
||||
angle_avail = common.bin2int(mb[25:27])
|
||||
if angle_avail == 0:
|
||||
return None, "", "N/A"
|
||||
else:
|
||||
angle = common.bin2int(mb[27:36])
|
||||
|
||||
if angle_avail == 1:
|
||||
angle_source = "MCP/FCU"
|
||||
elif angle_avail == 2:
|
||||
angle_source = "Autopilot mode"
|
||||
else:
|
||||
angle_source = "FMS/RNAV"
|
||||
|
||||
angle_type = "Heading" if int(mb[36]) else "Track"
|
||||
|
||||
return angle, angle_type, angle_source
|
||||
|
||||
|
||||
def baro_pressure_setting(msg: str) -> None | float:
|
||||
"""Decode barometric pressure setting.
|
||||
|
||||
Args:
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
float: Barometric pressure setting (millibars)
|
||||
|
||||
"""
|
||||
|
||||
if common.typecode(msg) != 29:
|
||||
raise RuntimeError(
|
||||
"%s: Not a target state and status message, expecting TC=29" % msg
|
||||
)
|
||||
|
||||
mb = common.hex2bin(msg)[32:]
|
||||
|
||||
subtype = common.bin2int(mb[5:7])
|
||||
|
||||
if subtype == 0:
|
||||
raise RuntimeError(
|
||||
"%s: ADS-B version 1 target state and status message does not "
|
||||
"contain barometric pressure setting" % msg
|
||||
)
|
||||
|
||||
baro = common.bin2int(mb[20:29])
|
||||
if baro == 0:
|
||||
return None
|
||||
|
||||
return 800 + (baro - 1) * 0.8
|
||||
|
||||
|
||||
def autopilot(msg) -> None | bool:
|
||||
"""Decode autopilot engagement.
|
||||
|
||||
Args:
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
bool: Autopilot engaged
|
||||
|
||||
"""
|
||||
|
||||
if common.typecode(msg) != 29:
|
||||
raise RuntimeError(
|
||||
"%s: Not a target state and status message, expecting TC=29" % msg
|
||||
)
|
||||
|
||||
mb = common.hex2bin(msg)[32:]
|
||||
|
||||
subtype = common.bin2int(mb[5:7])
|
||||
|
||||
if subtype == 0:
|
||||
raise RuntimeError(
|
||||
"%s: ADS-B version 1 target state and status message does not "
|
||||
"contain autopilot engagement" % msg
|
||||
)
|
||||
|
||||
if int(mb[46]) == 0:
|
||||
return None
|
||||
|
||||
autopilot = True if int(mb[47]) == 1 else False
|
||||
|
||||
return autopilot
|
||||
|
||||
|
||||
def vnav_mode(msg) -> None | bool:
|
||||
"""Decode VNAV mode.
|
||||
|
||||
Args:
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
bool: VNAV mode engaged
|
||||
|
||||
"""
|
||||
|
||||
if common.typecode(msg) != 29:
|
||||
raise RuntimeError(
|
||||
"%s: Not a target state and status message, expecting TC=29" % msg
|
||||
)
|
||||
|
||||
mb = common.hex2bin(msg)[32:]
|
||||
|
||||
subtype = common.bin2int(mb[5:7])
|
||||
|
||||
if subtype == 0:
|
||||
raise RuntimeError(
|
||||
"%s: ADS-B version 1 target state and status message does not "
|
||||
"contain vnav mode, use vertical mode instead" % msg
|
||||
)
|
||||
|
||||
if int(mb[46]) == 0:
|
||||
return None
|
||||
|
||||
vnav_mode = True if int(mb[48]) == 1 else False
|
||||
|
||||
return vnav_mode
|
||||
|
||||
|
||||
def altitude_hold_mode(msg) -> None | bool:
|
||||
"""Decode altitude hold mode.
|
||||
|
||||
Args:
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
bool: Altitude hold mode engaged
|
||||
|
||||
"""
|
||||
|
||||
if common.typecode(msg) != 29:
|
||||
raise RuntimeError(
|
||||
"%s: Not a target state and status message, expecting TC=29" % msg
|
||||
)
|
||||
|
||||
mb = common.hex2bin(msg)[32:]
|
||||
|
||||
subtype = common.bin2int(mb[5:7])
|
||||
|
||||
if subtype == 0:
|
||||
raise RuntimeError(
|
||||
"%s: ADS-B version 1 target state and status message does not "
|
||||
"contain altitude hold mode" % msg
|
||||
)
|
||||
|
||||
if int(mb[46]) == 0:
|
||||
return None
|
||||
|
||||
alt_hold_mode = True if int(mb[49]) == 1 else False
|
||||
|
||||
return alt_hold_mode
|
||||
|
||||
|
||||
def approach_mode(msg) -> None | bool:
|
||||
"""Decode approach mode.
|
||||
|
||||
Args:
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
bool: Approach mode engaged
|
||||
|
||||
"""
|
||||
|
||||
if common.typecode(msg) != 29:
|
||||
raise RuntimeError(
|
||||
"%s: Not a target state and status message, expecting TC=29" % msg
|
||||
)
|
||||
|
||||
mb = common.hex2bin(msg)[32:]
|
||||
|
||||
subtype = common.bin2int(mb[5:7])
|
||||
|
||||
if subtype == 0:
|
||||
raise RuntimeError(
|
||||
"%s: ADS-B version 1 target state and status message does not "
|
||||
"contain approach mode" % msg
|
||||
)
|
||||
|
||||
if int(mb[46]) == 0:
|
||||
return None
|
||||
|
||||
app_mode = True if int(mb[51]) == 1 else False
|
||||
|
||||
return app_mode
|
||||
|
||||
|
||||
def lnav_mode(msg) -> None | bool:
|
||||
"""Decode LNAV mode.
|
||||
|
||||
Args:
|
||||
msg (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
bool: LNAV mode engaged
|
||||
|
||||
"""
|
||||
|
||||
if common.typecode(msg) != 29:
|
||||
raise RuntimeError(
|
||||
"%s: Not a target state and status message, expecting TC=29" % msg
|
||||
)
|
||||
|
||||
mb = common.hex2bin(msg)[32:]
|
||||
|
||||
subtype = common.bin2int(mb[5:7])
|
||||
|
||||
if subtype == 0:
|
||||
raise RuntimeError(
|
||||
"%s: ADS-B version 1 target state and status message does not "
|
||||
"contain lnav mode, use horizontal mode instead" % msg
|
||||
)
|
||||
|
||||
if int(mb[46]) == 0:
|
||||
return None
|
||||
|
||||
lnav_mode = True if int(mb[53]) == 1 else False
|
||||
|
||||
return lnav_mode
|
||||
|
||||
|
||||
def tcas_operational(msg) -> None | bool:
|
||||
"""Decode TCAS/ACAS operational.
|
||||
|
||||
Args:
|
||||
msg (str): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
bool: TCAS/ACAS operational
|
||||
|
||||
"""
|
||||
|
||||
if common.typecode(msg) != 29:
|
||||
raise RuntimeError(
|
||||
"%s: Not a target state and status message, expecting TC=29" % msg
|
||||
)
|
||||
|
||||
mb = common.hex2bin(msg)[32:]
|
||||
|
||||
subtype = common.bin2int(mb[5:7])
|
||||
|
||||
if subtype == 0:
|
||||
tcas = True if int(mb[51]) == 0 else False
|
||||
else:
|
||||
tcas = True if int(mb[52]) == 1 else False
|
||||
|
||||
return tcas
|
||||
|
||||
|
||||
def tcas_ra(msg) -> bool:
|
||||
"""Decode TCAS/ACAS Resolution advisory.
|
||||
|
||||
Args:
|
||||
msg (str): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
bool: TCAS/ACAS Resolution advisory active
|
||||
|
||||
"""
|
||||
|
||||
if common.typecode(msg) != 29:
|
||||
raise RuntimeError(
|
||||
"%s: Not a target state and status message, expecting TC=29" % msg
|
||||
)
|
||||
|
||||
mb = common.hex2bin(msg)[32:]
|
||||
|
||||
subtype = common.bin2int(mb[5:7])
|
||||
|
||||
if subtype == 1:
|
||||
raise RuntimeError(
|
||||
"%s: ADS-B version 2 target state and status message does not "
|
||||
"contain TCAS/ACAS RA" % msg
|
||||
)
|
||||
|
||||
tcas_ra = True if int(mb[52]) == 1 else False
|
||||
|
||||
return tcas_ra
|
||||
|
||||
|
||||
def emergency_status(msg) -> int:
|
||||
"""Decode aircraft emergency status.
|
||||
|
||||
Value Meaning
|
||||
----- -----------------------
|
||||
0 No emergency
|
||||
1 General emergency
|
||||
2 Lifeguard/medical emergency
|
||||
3 Minimum fuel
|
||||
4 No communications
|
||||
5 Unlawful interference
|
||||
6 Downed aircraft
|
||||
7 Reserved
|
||||
|
||||
Args:
|
||||
msg (str): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
int: Emergency status
|
||||
|
||||
"""
|
||||
|
||||
if common.typecode(msg) != 29:
|
||||
raise RuntimeError(
|
||||
"%s: Not a target state and status message, expecting TC=29" % msg
|
||||
)
|
||||
|
||||
mb = common.hex2bin(msg)[32:]
|
||||
|
||||
subtype = common.bin2int(mb[5:7])
|
||||
|
||||
if subtype == 1:
|
||||
raise RuntimeError(
|
||||
"%s: ADS-B version 2 target state and status message does not "
|
||||
"contain emergency status" % msg
|
||||
)
|
||||
|
||||
return common.bin2int(mb[53:56])
|
||||
@@ -1,37 +1,88 @@
|
||||
"""Comm-B Wrapper.
|
||||
"""Comm-B module.
|
||||
|
||||
The Comm-B wrapper imports all functions from the following modules:
|
||||
The Comm-B module imports all functions from the following modules:
|
||||
|
||||
**ELS - elementary surveillance**
|
||||
- pyModeS.decoder.bds.bds10
|
||||
- pyModeS.decoder.bds.bds17
|
||||
- pyModeS.decoder.bds.bds20
|
||||
- pyModeS.decoder.bds.bds30
|
||||
ELS - elementary surveillance
|
||||
|
||||
**EHS - enhanced surveillance**
|
||||
- pyModeS.decoder.bds.bds40
|
||||
- pyModeS.decoder.bds.bds50
|
||||
- pyModeS.decoder.bds.bds60
|
||||
- pyModeS.decoder.bds.bds10
|
||||
- pyModeS.decoder.bds.bds17
|
||||
- pyModeS.decoder.bds.bds20
|
||||
- pyModeS.decoder.bds.bds30
|
||||
|
||||
**MRAR and MHR**
|
||||
- pyModeS.decoder.bds.bds44
|
||||
- pyModeS.decoder.bds.bds45
|
||||
EHS - enhanced surveillance
|
||||
|
||||
- pyModeS.decoder.bds.bds40
|
||||
- pyModeS.decoder.bds.bds50
|
||||
- pyModeS.decoder.bds.bds60
|
||||
|
||||
MRAR and MHR
|
||||
|
||||
- pyModeS.decoder.bds.bds44
|
||||
- pyModeS.decoder.bds.bds45
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
|
||||
# ELS - elementary surveillance
|
||||
from pyModeS.decoder.bds.bds10 import *
|
||||
from pyModeS.decoder.bds.bds17 import *
|
||||
from pyModeS.decoder.bds.bds20 import *
|
||||
from pyModeS.decoder.bds.bds30 import *
|
||||
from .bds.bds10 import is10, ovc10
|
||||
from .bds.bds17 import is17, cap17
|
||||
from .bds.bds20 import is20, cs20
|
||||
from .bds.bds30 import is30
|
||||
|
||||
# ELS - enhanced surveillance
|
||||
from pyModeS.decoder.bds.bds40 import *
|
||||
from pyModeS.decoder.bds.bds50 import *
|
||||
from pyModeS.decoder.bds.bds60 import *
|
||||
from .bds.bds40 import (
|
||||
is40,
|
||||
selalt40fms,
|
||||
selalt40mcp,
|
||||
p40baro,
|
||||
alt40fms,
|
||||
alt40mcp,
|
||||
)
|
||||
from .bds.bds50 import is50, roll50, trk50, gs50, rtrk50, tas50
|
||||
from .bds.bds60 import is60, hdg60, ias60, mach60, vr60baro, vr60ins
|
||||
|
||||
# MRAR and MHR
|
||||
from pyModeS.decoder.bds.bds44 import *
|
||||
from pyModeS.decoder.bds.bds45 import *
|
||||
from .bds.bds44 import is44, wind44, temp44, p44, hum44, turb44
|
||||
from .bds.bds45 import is45, turb45, ws45, mb45, ic45, wv45, temp45, p45, rh45
|
||||
|
||||
__all__ = [
|
||||
"is10",
|
||||
"ovc10",
|
||||
"is17",
|
||||
"cap17",
|
||||
"is20",
|
||||
"cs20",
|
||||
"is30",
|
||||
"is40",
|
||||
"selalt40fms",
|
||||
"selalt40mcp",
|
||||
"p40baro",
|
||||
"alt40fms",
|
||||
"alt40mcp",
|
||||
"is50",
|
||||
"roll50",
|
||||
"trk50",
|
||||
"gs50",
|
||||
"rtrk50",
|
||||
"tas50",
|
||||
"is60",
|
||||
"hdg60",
|
||||
"ias60",
|
||||
"mach60",
|
||||
"vr60baro",
|
||||
"vr60ins",
|
||||
"is44",
|
||||
"wind44",
|
||||
"temp44",
|
||||
"p44",
|
||||
"hum44",
|
||||
"turb44",
|
||||
"is45",
|
||||
"turb45",
|
||||
"ws45",
|
||||
"mb45",
|
||||
"ic45",
|
||||
"wv45",
|
||||
"temp45",
|
||||
"p45",
|
||||
"rh45",
|
||||
]
|
||||
|
||||
@@ -9,28 +9,58 @@ The EHS wrapper imports all functions from the following modules:
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
import warnings
|
||||
|
||||
from pyModeS.decoder.bds.bds40 import *
|
||||
from pyModeS.decoder.bds.bds50 import *
|
||||
from pyModeS.decoder.bds.bds60 import *
|
||||
from pyModeS.decoder.bds import infer
|
||||
from .bds.bds40 import (
|
||||
is40,
|
||||
selalt40fms,
|
||||
selalt40mcp,
|
||||
p40baro,
|
||||
alt40fms,
|
||||
alt40mcp,
|
||||
)
|
||||
from .bds.bds50 import is50, roll50, trk50, gs50, rtrk50, tas50
|
||||
from .bds.bds60 import is60, hdg60, ias60, mach60, vr60baro, vr60ins
|
||||
from .bds import infer
|
||||
|
||||
__all__ = [
|
||||
"is40",
|
||||
"selalt40fms",
|
||||
"selalt40mcp",
|
||||
"p40baro",
|
||||
"alt40fms",
|
||||
"alt40mcp",
|
||||
"is50",
|
||||
"roll50",
|
||||
"trk50",
|
||||
"gs50",
|
||||
"rtrk50",
|
||||
"tas50",
|
||||
"is60",
|
||||
"hdg60",
|
||||
"ias60",
|
||||
"mach60",
|
||||
"vr60baro",
|
||||
"vr60ins",
|
||||
"infer",
|
||||
]
|
||||
|
||||
warnings.simplefilter("once", DeprecationWarning)
|
||||
warnings.warn(
|
||||
"pms.ehs module is deprecated. Please use pms.commb instead.", DeprecationWarning
|
||||
"pms.ehs module is deprecated. Please use pms.commb instead.",
|
||||
DeprecationWarning,
|
||||
)
|
||||
|
||||
|
||||
def BDS(msg):
|
||||
warnings.warn(
|
||||
"pms.ehs.BDS() is deprecated, use pms.bds.infer() instead.", DeprecationWarning
|
||||
"pms.ehs.BDS() is deprecated, use pms.bds.infer() instead.",
|
||||
DeprecationWarning,
|
||||
)
|
||||
return infer(msg)
|
||||
|
||||
|
||||
def icao(msg):
|
||||
from pyModeS.decoder.common import icao
|
||||
from . import common
|
||||
|
||||
return icao(msg)
|
||||
return common.icao(msg)
|
||||
|
||||
@@ -10,16 +10,26 @@ 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.bds17 import *
|
||||
from pyModeS.decoder.bds.bds20 import *
|
||||
from pyModeS.decoder.bds.bds30 import *
|
||||
|
||||
import warnings
|
||||
|
||||
from .bds.bds10 import is10, ovc10
|
||||
from .bds.bds17 import cap17, is17
|
||||
from .bds.bds20 import cs20, is20
|
||||
from .bds.bds30 import is30
|
||||
|
||||
warnings.simplefilter("once", DeprecationWarning)
|
||||
warnings.warn(
|
||||
"pms.els module is deprecated. Please use pms.commb instead.", DeprecationWarning
|
||||
"pms.els module is deprecated. Please use pms.commb instead.",
|
||||
DeprecationWarning,
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"is10",
|
||||
"ovc10",
|
||||
"is17",
|
||||
"cap17",
|
||||
"is20",
|
||||
"cs20",
|
||||
"is30",
|
||||
]
|
||||
|
||||
25
pyModeS/decoder/flarm/__init__.py
Normal file
25
pyModeS/decoder/flarm/__init__.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from typing import TypedDict
|
||||
|
||||
from .decode import flarm as flarm_decode
|
||||
|
||||
__all__ = ["DecodedMessage", "flarm"]
|
||||
|
||||
|
||||
class DecodedMessage(TypedDict):
|
||||
timestamp: int
|
||||
icao24: str
|
||||
latitude: float
|
||||
longitude: float
|
||||
altitude: int
|
||||
vertical_speed: float
|
||||
groundspeed: int
|
||||
track: int
|
||||
type: str
|
||||
sensorLatitude: float
|
||||
sensorLongitude: float
|
||||
isIcao24: bool
|
||||
noTrack: bool
|
||||
stealth: bool
|
||||
|
||||
|
||||
flarm = flarm_decode
|
||||
124
pyModeS/decoder/flarm/core.c
Normal file
124
pyModeS/decoder/flarm/core.c
Normal file
@@ -0,0 +1,124 @@
|
||||
#include "core.h"
|
||||
|
||||
/*
|
||||
*
|
||||
* https://pastebin.com/YK2f8bfm
|
||||
*
|
||||
* NEW ENCRYPTION
|
||||
*
|
||||
* Swiss glider anti-colission system moved to a new encryption scheme: XXTEA
|
||||
* The algorithm encrypts all the packet after the header: total 20 bytes or 5 long int words of data
|
||||
*
|
||||
* XXTEA description and code are found here: http://en.wikipedia.org/wiki/XXTEA
|
||||
* The system uses 6 iterations of the main loop.
|
||||
*
|
||||
* The system version 6 sends two type of packets: position and ... some unknown data
|
||||
* The difference is made by bit 0 of byte 3 of the packet: for position data this bit is zero.
|
||||
*
|
||||
* For position data the key used depends on the time and transmitting device address.
|
||||
* The key is as well obscured by a weird algorithm.
|
||||
* The code to generate the key is:
|
||||
*
|
||||
* */
|
||||
|
||||
void make_key(int *key, long time, long address)
|
||||
{
|
||||
const long key1[4] = {0xe43276df, 0xdca83759, 0x9802b8ac, 0x4675a56b};
|
||||
const long key1b[4] = {0xfc78ea65, 0x804b90ea, 0xb76542cd, 0x329dfa32};
|
||||
const long *table = ((((time >> 23) & 255) & 0x01) != 0) ? key1b : key1;
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
key[i] = obscure(table[i] ^ ((time >> 6) ^ address), 0x045D9F3B) ^ 0x87B562F4;
|
||||
}
|
||||
}
|
||||
|
||||
long obscure(long key, unsigned long seed)
|
||||
{
|
||||
unsigned int m1 = seed * (key ^ (key >> 16));
|
||||
unsigned int m2 = seed * (m1 ^ (m1 >> 16));
|
||||
return m2 ^ (m2 >> 16);
|
||||
}
|
||||
|
||||
/*
|
||||
* NEW PACKET FORMAT:
|
||||
*
|
||||
* Byte Bits
|
||||
* 0 AAAA AAAA device address
|
||||
* 1 AAAA AAAA
|
||||
* 2 AAAA AAAA
|
||||
* 3 00aa 0000 aa = 10 or 01
|
||||
*
|
||||
* 4 vvvv vvvv vertical speed
|
||||
* 5 xxxx xxvv
|
||||
* 6 gggg gggg GPS status
|
||||
* 7 tttt gggg plane type
|
||||
*
|
||||
* 8 LLLL LLLL Latitude
|
||||
* 9 LLLL LLLL
|
||||
* 10 aaaa aLLL
|
||||
* 11 aaaa aaaa Altitude
|
||||
*
|
||||
* 12 NNNN NNNN Longitude
|
||||
* 13 NNNN NNNN
|
||||
* 14 xxxx NNNN
|
||||
* 15 FFxx xxxx multiplying factor
|
||||
*
|
||||
* 16 SSSS SSSS as in version 4
|
||||
* 17 ssss ssss
|
||||
* 18 KKKK KKKK
|
||||
* 19 kkkk kkkk
|
||||
*
|
||||
* 20 EEEE EEEE
|
||||
* 21 eeee eeee
|
||||
* 22 PPPP PPPP
|
||||
* 24 pppp pppp
|
||||
* */
|
||||
|
||||
/*
|
||||
* https://en.wikipedia.org/wiki/XXTEA
|
||||
*/
|
||||
|
||||
void btea(uint32_t *v, int n, uint32_t const key[4])
|
||||
{
|
||||
uint32_t y, z, sum;
|
||||
unsigned p, rounds, e;
|
||||
if (n > 1)
|
||||
{ /* Coding Part */
|
||||
/* Unused, should remove? */
|
||||
rounds = 6 + 52 / n;
|
||||
sum = 0;
|
||||
z = v[n - 1];
|
||||
do
|
||||
{
|
||||
sum += DELTA;
|
||||
e = (sum >> 2) & 3;
|
||||
for (p = 0; p < (unsigned)n - 1; p++)
|
||||
{
|
||||
y = v[p + 1];
|
||||
z = v[p] += MX;
|
||||
}
|
||||
y = v[0];
|
||||
z = v[n - 1] += MX;
|
||||
} while (--rounds);
|
||||
}
|
||||
else if (n < -1)
|
||||
{ /* Decoding Part */
|
||||
n = -n;
|
||||
rounds = 6; // + 52 / n;
|
||||
sum = rounds * DELTA;
|
||||
y = v[0];
|
||||
do
|
||||
{
|
||||
e = (sum >> 2) & 3;
|
||||
for (p = n - 1; p > 0; p--)
|
||||
{
|
||||
z = v[p - 1];
|
||||
y = v[p] -= MX;
|
||||
}
|
||||
z = v[n - 1];
|
||||
y = v[0] -= MX;
|
||||
sum -= DELTA;
|
||||
} while (--rounds);
|
||||
}
|
||||
}
|
||||
13
pyModeS/decoder/flarm/core.h
Normal file
13
pyModeS/decoder/flarm/core.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#ifndef __CORE_H__
|
||||
#define __CORE_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define DELTA 0x9e3779b9
|
||||
#define MX (((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4)) ^ ((sum ^ y) + (key[(p & 3) ^ e] ^ z)))
|
||||
|
||||
void make_key(int *key, long time, long address);
|
||||
long obscure(long key, unsigned long seed);
|
||||
void btea(uint32_t *v, int n, uint32_t const key[4]);
|
||||
|
||||
#endif
|
||||
4
pyModeS/decoder/flarm/core.pxd
Normal file
4
pyModeS/decoder/flarm/core.pxd
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
cdef extern from "core.h":
|
||||
void make_key(int*, long time, long address)
|
||||
void btea(int*, int, int*)
|
||||
14
pyModeS/decoder/flarm/decode.pyi
Normal file
14
pyModeS/decoder/flarm/decode.pyi
Normal file
@@ -0,0 +1,14 @@
|
||||
from typing import Any
|
||||
|
||||
from . import DecodedMessage
|
||||
|
||||
AIRCRAFT_TYPES: list[str]
|
||||
|
||||
|
||||
def flarm(
|
||||
timestamp: int,
|
||||
msg: str,
|
||||
refLat: float,
|
||||
refLon: float,
|
||||
**kwargs: Any,
|
||||
) -> DecodedMessage: ...
|
||||
147
pyModeS/decoder/flarm/decode.pyx
Normal file
147
pyModeS/decoder/flarm/decode.pyx
Normal file
@@ -0,0 +1,147 @@
|
||||
from cpython cimport array
|
||||
|
||||
from .core cimport make_key as c_make_key, btea as c_btea
|
||||
|
||||
import array
|
||||
import math
|
||||
from ctypes import c_byte
|
||||
from textwrap import wrap
|
||||
|
||||
AIRCRAFT_TYPES = [
|
||||
"Unknown", # 0
|
||||
"Glider", # 1
|
||||
"Tow-Plane", # 2
|
||||
"Helicopter", # 3
|
||||
"Parachute", # 4
|
||||
"Parachute Drop-Plane", # 5
|
||||
"Hangglider", # 6
|
||||
"Paraglider", # 7
|
||||
"Aircraft", # 8
|
||||
"Jet", # 9
|
||||
"UFO", # 10
|
||||
"Balloon", # 11
|
||||
"Airship", # 12
|
||||
"UAV", # 13
|
||||
"Reserved", # 14
|
||||
"Static Obstacle", # 15
|
||||
]
|
||||
|
||||
cdef long bytearray2int(str icao24):
|
||||
return (
|
||||
(int(icao24[4:6], 16) & 0xFF)
|
||||
| ((int(icao24[2:4], 16) & 0xFF) << 8)
|
||||
| ((int(icao24[:2], 16) & 0xFF) << 16)
|
||||
)
|
||||
|
||||
cpdef array.array make_key(long timestamp, str icao24):
|
||||
cdef long addr = bytearray2int(icao24)
|
||||
cdef array.array a = array.array('i', [0, 0, 0, 0])
|
||||
c_make_key(a.data.as_ints, timestamp, (addr << 8) & 0xffffff)
|
||||
return a
|
||||
|
||||
cpdef array.array btea(long timestamp, str msg):
|
||||
cdef int p
|
||||
cdef str icao24 = msg[4:6] + msg[2:4] + msg[:2]
|
||||
cdef array.array key = make_key(timestamp, icao24)
|
||||
|
||||
pieces = wrap(msg[8:], 8)
|
||||
cdef array.array toDecode = array.array('i', len(pieces) * [0])
|
||||
for i, piece in enumerate(pieces):
|
||||
p = 0
|
||||
for elt in wrap(piece, 2)[::-1]:
|
||||
p = (p << 8) + int(elt, 16)
|
||||
toDecode[i] = p
|
||||
|
||||
c_btea(toDecode.data.as_ints, -5, key.data.as_ints)
|
||||
return toDecode
|
||||
|
||||
cdef float velocity(int ns, int ew):
|
||||
return math.hypot(ew / 4, ns / 4)
|
||||
|
||||
def heading(ns, ew, velocity):
|
||||
if velocity < 1e-6:
|
||||
velocity = 1
|
||||
return (math.atan2(ew / velocity / 4, ns / velocity / 4) / 0.01745) % 360
|
||||
|
||||
def turningRate(a1, a2):
|
||||
return ((((a2 - a1)) + 540) % 360) - 180
|
||||
|
||||
def flarm(long timestamp, str msg, float refLat, float refLon, **kwargs):
|
||||
"""Decode a FLARM message.
|
||||
|
||||
Args:
|
||||
timestamp (int)
|
||||
msg (str)
|
||||
refLat (float): the receiver's location
|
||||
refLon (float): the receiver's location
|
||||
|
||||
Returns:
|
||||
a dictionary with all decoded fields. Any extra keyword argument passed
|
||||
is included in the output dictionary.
|
||||
"""
|
||||
cdef str icao24 = msg[4:6] + msg[2:4] + msg[:2]
|
||||
cdef int magic = int(msg[6:8], 16)
|
||||
|
||||
if magic != 0x10 and magic != 0x20:
|
||||
return None
|
||||
|
||||
cdef array.array decoded = btea(timestamp, msg)
|
||||
|
||||
cdef int aircraft_type = (decoded[0] >> 28) & 0xF
|
||||
cdef int gps = (decoded[0] >> 16) & 0xFFF
|
||||
cdef int raw_vs = c_byte(decoded[0] & 0x3FF).value
|
||||
|
||||
noTrack = ((decoded[0] >> 14) & 0x1) == 1
|
||||
stealth = ((decoded[0] >> 13) & 0x1) == 1
|
||||
|
||||
cdef int altitude = (decoded[1] >> 19) & 0x1FFF
|
||||
|
||||
cdef int lat = decoded[1] & 0x7FFFF
|
||||
|
||||
cdef int mult_factor = 1 << ((decoded[2] >> 30) & 0x3)
|
||||
cdef int lon = decoded[2] & 0xFFFFF
|
||||
|
||||
ns = list(
|
||||
c_byte((decoded[3] >> (i * 8)) & 0xFF).value * mult_factor
|
||||
for i in range(4)
|
||||
)
|
||||
ew = list(
|
||||
c_byte((decoded[4] >> (i * 8)) & 0xFF).value * mult_factor
|
||||
for i in range(4)
|
||||
)
|
||||
|
||||
cdef int roundLat = int(refLat * 1e7) >> 7
|
||||
lat = (lat - roundLat) % 0x080000
|
||||
if lat >= 0x040000:
|
||||
lat -= 0x080000
|
||||
lat = (((lat + roundLat) << 7) + 0x40)
|
||||
|
||||
roundLon = int(refLon * 1e7) >> 7
|
||||
lon = (lon - roundLon) % 0x100000
|
||||
if lon >= 0x080000:
|
||||
lon -= 0x100000
|
||||
lon = (((lon + roundLon) << 7) + 0x40)
|
||||
|
||||
speed = sum(velocity(n, e) for n, e in zip(ns, ew)) / 4
|
||||
|
||||
heading4 = heading(ns[0], ew[0], speed)
|
||||
heading8 = heading(ns[1], ew[1], speed)
|
||||
|
||||
return dict(
|
||||
timestamp=timestamp,
|
||||
icao24=icao24,
|
||||
latitude=lat * 1e-7,
|
||||
longitude=lon * 1e-7,
|
||||
geoaltitude=altitude,
|
||||
vertical_speed=raw_vs * mult_factor / 10,
|
||||
groundspeed=speed,
|
||||
track=heading4 - 4 * turningRate(heading4, heading8) / 4,
|
||||
type=AIRCRAFT_TYPES[aircraft_type],
|
||||
sensorLatitude=refLat,
|
||||
sensorLongitude=refLon,
|
||||
isIcao24=magic==0x10,
|
||||
noTrack=noTrack,
|
||||
stealth=stealth,
|
||||
gps=gps,
|
||||
**kwargs
|
||||
)
|
||||
@@ -1,23 +1,138 @@
|
||||
# Copyright (C) 2018 Junzi Sun (TU Delft)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Warpper for short roll call surveillance replies DF=4/5
|
||||
|
||||
[To be implemented]
|
||||
Decode short roll call surveillance replies, with downlink format 4 or 5
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, print_function, division
|
||||
from pyModeS.decoder import common
|
||||
from __future__ import annotations
|
||||
from typing import Callable, TypeVar
|
||||
|
||||
from .. import common
|
||||
|
||||
T = TypeVar("T")
|
||||
F = Callable[[str], T]
|
||||
|
||||
|
||||
def _checkdf(func: F[T]) -> F[T]:
|
||||
"""Ensure downlink format is 4 or 5."""
|
||||
|
||||
def wrapper(msg: str) -> T:
|
||||
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: str) -> tuple[int, str]:
|
||||
"""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 = ""
|
||||
|
||||
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: str) -> tuple[int, str]:
|
||||
"""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 = ""
|
||||
|
||||
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: str) -> tuple[int, int, None | str]:
|
||||
"""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: str) -> None | int:
|
||||
"""Decode altitude.
|
||||
|
||||
Args:
|
||||
msg (String): 14 hexdigits string
|
||||
|
||||
Returns:
|
||||
int: altitude in ft
|
||||
|
||||
"""
|
||||
return common.altcode(msg)
|
||||
|
||||
|
||||
@_checkdf
|
||||
def identity(msg: str) -> str:
|
||||
"""Decode squawk code.
|
||||
|
||||
Args:
|
||||
msg (String): 14 hexdigits string
|
||||
|
||||
Returns:
|
||||
string: squawk code
|
||||
|
||||
"""
|
||||
return common.idcode(msg)
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
"""Uncertainty parameters.
|
||||
|
||||
See source code at: https://github.com/junzis/pyModeS/blob/master/pyModeS/decoder/uncertainty.py
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
if sys.version_info < (3, 8):
|
||||
from typing_extensions import TypedDict
|
||||
else:
|
||||
from typing import TypedDict
|
||||
|
||||
NA = None
|
||||
|
||||
TC_NUCp_lookup = {
|
||||
@@ -26,7 +34,7 @@ TC_NUCp_lookup = {
|
||||
22: 0,
|
||||
}
|
||||
|
||||
TC_NICv1_lookup = {
|
||||
TC_NICv1_lookup: dict[int, int | dict[int, int]] = {
|
||||
5: 11,
|
||||
6: 10,
|
||||
7: 9,
|
||||
@@ -46,7 +54,7 @@ TC_NICv1_lookup = {
|
||||
22: 0,
|
||||
}
|
||||
|
||||
TC_NICv2_lookup = {
|
||||
TC_NICv2_lookup: dict[int, int | dict[int, int]] = {
|
||||
5: 11,
|
||||
6: 10,
|
||||
7: {2: 9, 0: 8},
|
||||
@@ -67,7 +75,13 @@ TC_NICv2_lookup = {
|
||||
}
|
||||
|
||||
|
||||
NUCp = {
|
||||
class NUCpEntry(TypedDict):
|
||||
HPL: None | float
|
||||
RCu: None | int
|
||||
RCv: None | int
|
||||
|
||||
|
||||
NUCp: dict[int, NUCpEntry] = {
|
||||
9: {"HPL": 7.5, "RCu": 3, "RCv": 4},
|
||||
8: {"HPL": 25, "RCu": 10, "RCv": 15},
|
||||
7: {"HPL": 185, "RCu": 93, "RCv": NA},
|
||||
@@ -80,7 +94,13 @@ NUCp = {
|
||||
0: {"HPL": NA, "RCu": NA, "RCv": NA},
|
||||
}
|
||||
|
||||
NUCv = {
|
||||
|
||||
class NUCvEntry(TypedDict):
|
||||
HVE: None | float
|
||||
VVE: None | float
|
||||
|
||||
|
||||
NUCv: dict[int, NUCvEntry] = {
|
||||
0: {"HVE": NA, "VVE": NA},
|
||||
1: {"HVE": 10, "VVE": 15.2},
|
||||
2: {"HVE": 3, "VVE": 4.5},
|
||||
@@ -88,7 +108,13 @@ NUCv = {
|
||||
4: {"HVE": 0.3, "VVE": 0.46},
|
||||
}
|
||||
|
||||
NACp = {
|
||||
|
||||
class NACpEntry(TypedDict):
|
||||
EPU: None | int
|
||||
VEPU: None | int
|
||||
|
||||
|
||||
NACp: dict[int, NACpEntry] = {
|
||||
11: {"EPU": 3, "VEPU": 4},
|
||||
10: {"EPU": 10, "VEPU": 15},
|
||||
9: {"EPU": 30, "VEPU": 45},
|
||||
@@ -103,7 +129,13 @@ NACp = {
|
||||
0: {"EPU": NA, "VEPU": NA},
|
||||
}
|
||||
|
||||
NACv = {
|
||||
|
||||
class NACvEntry(TypedDict):
|
||||
HFOMr: None | float
|
||||
VFOMr: None | float
|
||||
|
||||
|
||||
NACv: dict[int, NACvEntry] = {
|
||||
0: {"HFOMr": NA, "VFOMr": NA},
|
||||
1: {"HFOMr": 10, "VFOMr": 15.2},
|
||||
2: {"HFOMr": 3, "VFOMr": 4.5},
|
||||
@@ -111,7 +143,13 @@ NACv = {
|
||||
4: {"HFOMr": 0.3, "VFOMr": 0.46},
|
||||
}
|
||||
|
||||
SIL = {
|
||||
|
||||
class SILEntry(TypedDict):
|
||||
PE_RCu: None | float
|
||||
PE_VPL: None | float
|
||||
|
||||
|
||||
SIL: dict[int, SILEntry] = {
|
||||
3: {"PE_RCu": 1e-7, "PE_VPL": 2e-7},
|
||||
2: {"PE_RCu": 1e-5, "PE_VPL": 1e-5},
|
||||
1: {"PE_RCu": 1e-3, "PE_VPL": 1e-3},
|
||||
@@ -119,7 +157,12 @@ SIL = {
|
||||
}
|
||||
|
||||
|
||||
NICv1 = {
|
||||
class NICv1Entry(TypedDict):
|
||||
Rc: None | float
|
||||
VPL: None | float
|
||||
|
||||
|
||||
NICv1: dict[int, dict[int, NICv1Entry]] = {
|
||||
# NIC is used as the index at second Level
|
||||
11: {0: {"Rc": 7.5, "VPL": 11}},
|
||||
10: {0: {"Rc": 25, "VPL": 37.5}},
|
||||
@@ -135,7 +178,12 @@ NICv1 = {
|
||||
0: {0: {"Rc": NA, "VPL": NA}},
|
||||
}
|
||||
|
||||
NICv2 = {
|
||||
|
||||
class NICv2Entry(TypedDict):
|
||||
Rc: None | float
|
||||
|
||||
|
||||
NICv2: dict[int, dict[int, NICv2Entry]] = {
|
||||
# Decimal value of [NICa NICb/NICc] is used as the index at second Level
|
||||
11: {0: {"Rc": 7.5}},
|
||||
10: {0: {"Rc": 25}},
|
||||
|
||||
223
pyModeS/decoder/uplink.py
Normal file
223
pyModeS/decoder/uplink.py
Normal file
@@ -0,0 +1,223 @@
|
||||
from typing import Optional
|
||||
from .. import common
|
||||
from textwrap import wrap
|
||||
|
||||
|
||||
def uplink_icao(msg: str) -> str:
|
||||
"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: str) -> int:
|
||||
"""Decode Uplink Format value, bits 1 to 5."""
|
||||
ufbin = common.hex2bin(msg[:2])
|
||||
return min(common.bin2int(ufbin[0:5]), 24)
|
||||
|
||||
|
||||
def bds(msg: str) -> Optional[str]:
|
||||
"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 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) << 3) | ((mbytes[3] & 0xE0) >> 5)
|
||||
BDS2 = RRS
|
||||
else:
|
||||
# for other values of DI, the BDS2 is assumed 0
|
||||
# (as per ICAO Annex 10 Vol IV)
|
||||
BDS2 = 0
|
||||
|
||||
return str(format(BDS1,"X")) + str(format(BDS2,"X"))
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def pr(msg: str) -> Optional[int]:
|
||||
"""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: str) -> Optional[str]:
|
||||
"""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
|
||||
if UF == 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 in {4, 5, 20, 21}:
|
||||
di = mbytes[1] & 0x7
|
||||
RR = mbytes[1] >> 3 & 0x1F
|
||||
if RR > 15:
|
||||
BDS1 = RR - 16 # noqa: F841
|
||||
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 = ""
|
||||
IC = ""
|
||||
lockout = False
|
||||
di = ""
|
||||
RR = ""
|
||||
RRS = ""
|
||||
BDS = ""
|
||||
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) << 3) | ((mbytes[3] & 0xE0) >> 5)
|
||||
BDS2 = RRS
|
||||
if RR > 15:
|
||||
BDS = str(format(BDS1,"X")) + str(format(BDS2,"X"))
|
||||
|
||||
return {
|
||||
"DI": di,
|
||||
"IC": IC,
|
||||
"LOS": lockout,
|
||||
"PR": PR,
|
||||
"RR": RR,
|
||||
"RRS": RRS,
|
||||
"BDS": BDS,
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
from __future__ import absolute_import, print_function, division
|
||||
|
||||
@@ -9,15 +9,15 @@ International Standard Atmosphere
|
||||
|
||||
p,rho,T = atmos(H) # atmos as function of geopotential altitude H [m]
|
||||
a = vsound(H) # speed of sound [m/s] as function of H[m]
|
||||
p = pressure(H) # calls atmos but retruns only pressure [Pa]
|
||||
p = pressure(H) # calls atmos but returns only pressure [Pa]
|
||||
T = temperature(H) # calculates temperature [K]
|
||||
rho = density(H) # calls atmos but retruns only pressure [Pa]
|
||||
rho = density(H) # calls atmos but returns only pressure [Pa]
|
||||
|
||||
Speed conversion at altitude H[m] in ISA
|
||||
::
|
||||
|
||||
Mach = tas2mach(Vtas,H) # true airspeed (Vtas) to mach number conversion
|
||||
Vtas = mach2tas(Mach,H) # true airspeed (Vtas) to mach number conversion
|
||||
Vtas = mach2tas(Mach,H) # mach number to true airspeed (Vtas) conversion
|
||||
Vtas = eas2tas(Veas,H) # equivalent airspeed to true airspeed, H in [m]
|
||||
Veas = tas2eas(Vtas,H) # true airspeed to equivent airspeed, H in [m]
|
||||
Vtas = cas2tas(Vcas,H) # Vcas to Vtas conversion both m/s, H in [m]
|
||||
@@ -35,18 +35,18 @@ ft = 0.3048 # ft -> m
|
||||
fpm = 0.00508 # ft/min -> m/s
|
||||
inch = 0.0254 # inch -> m
|
||||
sqft = 0.09290304 # 1 square foot
|
||||
nm = 1852.0 # nautical mile -> m
|
||||
nm = 1852 # nautical mile -> m
|
||||
lbs = 0.453592 # pound -> kg
|
||||
g0 = 9.80665 # m/s2, Sea level gravity constant
|
||||
R = 287.05287 # m2/(s2 x K), gas constant, sea level ISA
|
||||
p0 = 101325.0 # Pa, air pressure, sea level ISA
|
||||
p0 = 101325 # Pa, air pressure, sea level ISA
|
||||
rho0 = 1.225 # kg/m3, air density, sea level ISA
|
||||
T0 = 288.15 # K, temperature, sea level ISA
|
||||
gamma = 1.40 # cp/cv for air
|
||||
gamma1 = 0.2 # (gamma-1)/2 for air
|
||||
gamma2 = 3.5 # gamma/(gamma-1) for air
|
||||
beta = -0.0065 # [K/m] ISA temp gradient below tropopause
|
||||
r_earth = 6371000.0 # m, average earth radius
|
||||
r_earth = 6371000 # m, average earth radius
|
||||
a0 = 340.293988 # m/s, sea level speed of sound ISA, sqrt(gamma*R*T0)
|
||||
|
||||
|
||||
@@ -94,8 +94,8 @@ def distance(lat1, lon1, lat2, lon2, H=0):
|
||||
"""
|
||||
|
||||
# phi = 90 - latitude
|
||||
phi1 = np.radians(90.0 - lat1)
|
||||
phi2 = np.radians(90.0 - lat2)
|
||||
phi1 = np.radians(90 - lat1)
|
||||
phi2 = np.radians(90 - lat2)
|
||||
|
||||
# theta = longitude
|
||||
theta1 = np.radians(lon1)
|
||||
@@ -158,16 +158,16 @@ def tas2eas(Vtas, H):
|
||||
def cas2tas(Vcas, H):
|
||||
"""Calibrated Airspeed to True Airspeed"""
|
||||
p, rho, T = atmos(H)
|
||||
qdyn = p0 * ((1.0 + rho0 * Vcas * Vcas / (7.0 * p0)) ** 3.5 - 1.0)
|
||||
Vtas = np.sqrt(7.0 * p / rho * ((1.0 + qdyn / p) ** (2.0 / 7.0) - 1.0))
|
||||
qdyn = p0 * ((1 + rho0 * Vcas * Vcas / (7 * p0)) ** 3.5 - 1.0)
|
||||
Vtas = np.sqrt(7 * p / rho * ((1 + qdyn / p) ** (2 / 7.0) - 1.0))
|
||||
return Vtas
|
||||
|
||||
|
||||
def tas2cas(Vtas, H):
|
||||
"""True Airspeed to Calibrated Airspeed"""
|
||||
p, rho, T = atmos(H)
|
||||
qdyn = p * ((1.0 + rho * Vtas * Vtas / (7.0 * p)) ** 3.5 - 1.0)
|
||||
Vcas = np.sqrt(7.0 * p0 / rho0 * ((qdyn / p0 + 1.0) ** (2.0 / 7.0) - 1.0))
|
||||
qdyn = p * ((1 + rho * Vtas * Vtas / (7 * p)) ** 3.5 - 1.0)
|
||||
Vcas = np.sqrt(7 * p0 / rho0 * ((qdyn / p0 + 1.0) ** (2 / 7.0) - 1.0))
|
||||
return Vcas
|
||||
|
||||
|
||||
|
||||
@@ -1,93 +1,132 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
import traceback
|
||||
import numpy as np
|
||||
import pyModeS as pms
|
||||
from rtlsdr import RtlSdr
|
||||
import time
|
||||
|
||||
modes_sample_rate = 2e6
|
||||
from typing import Any
|
||||
|
||||
|
||||
import_msg = """
|
||||
---------------------------------------------------------------------
|
||||
Warning: pyrtlsdr not installed (required for using RTL-SDR devices)!
|
||||
---------------------------------------------------------------------"""
|
||||
|
||||
try:
|
||||
import rtlsdr # type: ignore
|
||||
except ImportError:
|
||||
print(import_msg)
|
||||
|
||||
sampling_rate = 2e6
|
||||
smaples_per_microsec = 2
|
||||
|
||||
modes_frequency = 1090e6
|
||||
buffer_size = 1024 * 100
|
||||
read_size = 1024 * 20
|
||||
buffer_size = 1024 * 200
|
||||
read_size = 1024 * 100
|
||||
|
||||
pbits = 8
|
||||
fbits = 112
|
||||
preamble = [1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0]
|
||||
th_amp = 0.2 # signal amplitude threshold for 0 and 1 bit
|
||||
th_amp_diff = 0.8 # signal amplitude threshold difference between 0 and 1 bit
|
||||
|
||||
|
||||
class RtlReader(object):
|
||||
def __init__(self, **kwargs):
|
||||
def __init__(self, **kwargs) -> None:
|
||||
super(RtlReader, self).__init__()
|
||||
self.signal_buffer = []
|
||||
self.sdr = RtlSdr()
|
||||
self.sdr.sample_rate = modes_sample_rate
|
||||
self.signal_buffer: list[float] = [] # amplitude of the sample only
|
||||
self.sdr = rtlsdr.RtlSdr()
|
||||
self.sdr.sample_rate = sampling_rate
|
||||
self.sdr.center_freq = modes_frequency
|
||||
self.sdr.gain = "auto"
|
||||
# sdr.freq_correction = 75
|
||||
|
||||
self.debug = kwargs.get("debug", False)
|
||||
self.raw_pipe_in = None
|
||||
self.stop_flag = False
|
||||
self.noise_floor = 1e6
|
||||
|
||||
def _process_buffer(self):
|
||||
self.exception_queue = None
|
||||
|
||||
def _calc_noise(self) -> float:
|
||||
"""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) -> list[list[Any]]:
|
||||
"""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 = []
|
||||
|
||||
# signal_array = np.array(self.signal_buffer)
|
||||
# pulses_array = np.where(np.array(self.signal_buffer) < th_amp, 0, 1)
|
||||
# pulses = "".join(str(x) for x in pulses_array)
|
||||
buffer_length = len(self.signal_buffer)
|
||||
|
||||
i = 0
|
||||
while i < buffer_length:
|
||||
if self.signal_buffer[i] < th_amp:
|
||||
if self.signal_buffer[i] < min_sig_amp:
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# if pulses[i : i + pbits * 2] == preamble:
|
||||
if self._check_preamble(self.signal_buffer[i : i + pbits * 2]):
|
||||
frame_start = i + pbits * 2
|
||||
frame_end = i + pbits * 2 + (fbits + 1) * 2
|
||||
frame_start = i + pbits * 2
|
||||
if self._check_preamble(self.signal_buffer[i:frame_start]):
|
||||
frame_length = (fbits + 1) * 2
|
||||
frame_end = frame_start + frame_length
|
||||
frame_pulses = self.signal_buffer[frame_start:frame_end]
|
||||
|
||||
msgbin = ""
|
||||
threshold = max(frame_pulses) * 0.2
|
||||
|
||||
msgbin: list[int] = []
|
||||
for j in range(0, frame_length, 2):
|
||||
p2 = frame_pulses[j : j + 2]
|
||||
j_2 = j + 2
|
||||
p2 = frame_pulses[j:j_2]
|
||||
if len(p2) < 2:
|
||||
break
|
||||
|
||||
if p2[0] < th_amp and p2[1] < th_amp:
|
||||
if p2[0] < threshold and p2[1] < threshold:
|
||||
break
|
||||
elif p2[0] >= p2[1]:
|
||||
c = "1"
|
||||
c = 1
|
||||
elif p2[0] < p2[1]:
|
||||
c = "0"
|
||||
c = 0
|
||||
else:
|
||||
msgbin = ""
|
||||
msgbin = []
|
||||
break
|
||||
msgbin += c
|
||||
|
||||
msgbin.append(c)
|
||||
|
||||
# advance i with a jump
|
||||
i = frame_start + j
|
||||
|
||||
if len(msgbin) > 0:
|
||||
msghex = pms.bin2hex(msgbin)
|
||||
msghex = pms.bin2hex("".join([str(i) for i in msgbin]))
|
||||
if self._check_msg(msghex):
|
||||
messages.append([msghex, time.time()])
|
||||
if self.debug:
|
||||
self._debug_msg(msghex)
|
||||
|
||||
elif i > buffer_length - 500:
|
||||
# save some for next process
|
||||
break
|
||||
# elif i > buffer_length - 500:
|
||||
# # save some for next process
|
||||
# break
|
||||
else:
|
||||
i += 1
|
||||
|
||||
# keep reminder of buffer for next iteration
|
||||
# reset the buffer
|
||||
self.signal_buffer = self.signal_buffer[i:]
|
||||
|
||||
return messages
|
||||
|
||||
def _check_preamble(self, pulses):
|
||||
def _check_preamble(self, pulses) -> bool:
|
||||
if len(pulses) != 16:
|
||||
return False
|
||||
|
||||
@@ -97,7 +136,7 @@ class RtlReader(object):
|
||||
|
||||
return True
|
||||
|
||||
def _check_msg(self, msg):
|
||||
def _check_msg(self, msg) -> bool:
|
||||
df = pms.df(msg)
|
||||
msglen = len(msg)
|
||||
if df == 17 and msglen == 28:
|
||||
@@ -107,8 +146,9 @@ class RtlReader(object):
|
||||
return True
|
||||
elif df in [4, 5, 11] and msglen == 14:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _debug_msg(self, msg):
|
||||
def _debug_msg(self, msg) -> None:
|
||||
df = pms.df(msg)
|
||||
msglen = len(msg)
|
||||
if df == 17 and msglen == 28:
|
||||
@@ -121,35 +161,42 @@ class RtlReader(object):
|
||||
# print("[*]", msg)
|
||||
pass
|
||||
|
||||
def _read_callback(self, data, rtlsdr_obj):
|
||||
# scaling signal (imporatant)
|
||||
def _read_callback(self, data, rtlsdr_obj) -> None:
|
||||
amp = np.absolute(data)
|
||||
amp_norm = np.interp(amp, (amp.min(), amp.max()), (0, 1))
|
||||
self.signal_buffer.extend(amp_norm.tolist())
|
||||
self.signal_buffer.extend(amp.tolist())
|
||||
|
||||
if len(self.signal_buffer) >= buffer_size:
|
||||
messages = self._process_buffer()
|
||||
self.handle_messages(messages)
|
||||
|
||||
def handle_messages(self, messages):
|
||||
def handle_messages(self, messages) -> None:
|
||||
"""re-implement this method to handle the messages"""
|
||||
for msg, t in messages:
|
||||
# print("%15.9f %s" % (t, msg))
|
||||
pass
|
||||
|
||||
def stop(self, *args, **kwargs):
|
||||
self.sdr.cancel_read_async()
|
||||
def stop(self, *args, **kwargs) -> None:
|
||||
self.sdr.close()
|
||||
|
||||
def run(self, raw_pipe_in=None, stop_flag=None):
|
||||
def run(
|
||||
self, raw_pipe_in=None, stop_flag=None, exception_queue=None
|
||||
) -> None:
|
||||
self.raw_pipe_in = raw_pipe_in
|
||||
self.exception_queue = exception_queue
|
||||
self.stop_flag = stop_flag
|
||||
self.sdr.read_samples_async(self._read_callback, read_size)
|
||||
|
||||
# count = 1
|
||||
# while count < 1000:
|
||||
# count += 1
|
||||
# data = self.sdr.read_samples(read_size)
|
||||
# self._read_callback(data, None)
|
||||
try:
|
||||
# raise RuntimeError("test exception")
|
||||
|
||||
while True:
|
||||
data = self.sdr.read_samples(read_size)
|
||||
self._read_callback(data, None)
|
||||
|
||||
except Exception as e:
|
||||
tb = traceback.format_exc()
|
||||
if self.exception_queue is not None:
|
||||
self.exception_queue.put(tb)
|
||||
raise e
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""Stream beast raw data from a TCP server, convert to mode-s messages."""
|
||||
|
||||
from __future__ import print_function, division
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
@@ -8,11 +7,6 @@ import pyModeS as pms
|
||||
import traceback
|
||||
import zmq
|
||||
|
||||
if sys.version_info > (3, 0):
|
||||
PY_VERSION = 3
|
||||
else:
|
||||
PY_VERSION = 2
|
||||
|
||||
|
||||
class TcpClient(object):
|
||||
def __init__(self, host, port, datatype):
|
||||
@@ -29,6 +23,8 @@ class TcpClient(object):
|
||||
self.raw_pipe_in = None
|
||||
self.stop_flag = False
|
||||
|
||||
self.exception_queue = None
|
||||
|
||||
def connect(self):
|
||||
self.socket = zmq.Context().socket(zmq.STREAM)
|
||||
self.socket.setsockopt(zmq.LINGER, 0)
|
||||
@@ -36,7 +32,7 @@ class TcpClient(object):
|
||||
self.socket.connect("tcp://%s:%s" % (self.host, self.port))
|
||||
|
||||
def stop(self):
|
||||
self.socket.disconnect()
|
||||
self.socket.close()
|
||||
|
||||
def read_raw_buffer(self):
|
||||
""" Read raw ADS-B data type.
|
||||
@@ -170,7 +166,7 @@ class TcpClient(object):
|
||||
Start character '$'
|
||||
|
||||
MS field - Payload
|
||||
Postion 1 through 14:
|
||||
Position 1 through 14:
|
||||
14 bytes = 112 bits
|
||||
Mode-S payload
|
||||
In case of DF types that only carry 7 bytes of information
|
||||
@@ -254,8 +250,9 @@ class TcpClient(object):
|
||||
for msg, t in messages:
|
||||
print("%15.9f %s" % (t, msg))
|
||||
|
||||
def run(self, raw_pipe_in=None, stop_flag=None):
|
||||
def run(self, raw_pipe_in=None, stop_flag=None, exception_queue=None):
|
||||
self.raw_pipe_in = raw_pipe_in
|
||||
self.exception_queue = exception_queue
|
||||
self.stop_flag = stop_flag
|
||||
self.connect()
|
||||
|
||||
@@ -263,17 +260,9 @@ class TcpClient(object):
|
||||
try:
|
||||
received = [i for i in self.socket.recv(4096)]
|
||||
|
||||
if PY_VERSION == 2:
|
||||
received = [ord(i) for i in received]
|
||||
|
||||
self.buffer.extend(received)
|
||||
# print(''.join(x.encode('hex') for x in self.buffer))
|
||||
|
||||
# process self.buffer when it is longer enough
|
||||
# if len(self.buffer) < 2048:
|
||||
# continue
|
||||
# -- Removed!! Cause delay in low data rate scenario --
|
||||
|
||||
if self.datatype == "beast":
|
||||
messages = self.read_beast_buffer()
|
||||
elif self.datatype == "raw":
|
||||
@@ -286,22 +275,15 @@ class TcpClient(object):
|
||||
else:
|
||||
self.handle_messages(messages)
|
||||
|
||||
except Exception as e:
|
||||
# Provides the user an option to supply the environment
|
||||
# variable PYMODES_DEBUG to halt the execution
|
||||
# for debugging purposes
|
||||
debug_intent = os.environ.get("PYMODES_DEBUG", "false")
|
||||
if debug_intent.lower() == "true":
|
||||
traceback.print_exc()
|
||||
sys.exit()
|
||||
else:
|
||||
print("Unexpected Error:", e)
|
||||
# raise RuntimeError("test exception")
|
||||
|
||||
try:
|
||||
sock = self.connect()
|
||||
time.sleep(1)
|
||||
except Exception as e:
|
||||
print("Unexpected Error:", e)
|
||||
except zmq.error.Again:
|
||||
continue
|
||||
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__":
|
||||
@@ -310,4 +292,7 @@ if __name__ == "__main__":
|
||||
port = int(sys.argv[2])
|
||||
datatype = sys.argv[3]
|
||||
client = TcpClient(host=host, port=port, datatype=datatype)
|
||||
client.run()
|
||||
try:
|
||||
client.run()
|
||||
finally:
|
||||
client.stop()
|
||||
|
||||
0
pyModeS/py.typed
Normal file
0
pyModeS/py.typed
Normal file
@@ -1,61 +1,46 @@
|
||||
from __future__ import absolute_import, print_function, division
|
||||
from typing import Optional
|
||||
|
||||
import numpy as np
|
||||
from textwrap import wrap
|
||||
|
||||
|
||||
def hex2bin(hexstr):
|
||||
"""Convert a hexdecimal string to binary string, with zero fillings."""
|
||||
def hex2bin(hexstr: str) -> str:
|
||||
"""Convert a hexadecimal string to binary string, with zero fillings."""
|
||||
num_of_bits = len(hexstr) * 4
|
||||
binstr = bin(int(hexstr, 16))[2:].zfill(int(num_of_bits))
|
||||
return binstr
|
||||
|
||||
|
||||
def hex2int(hexstr):
|
||||
"""Convert a hexdecimal string to integer."""
|
||||
def hex2int(hexstr: str) -> int:
|
||||
"""Convert a hexadecimal string to integer."""
|
||||
return int(hexstr, 16)
|
||||
|
||||
|
||||
def int2hex(n):
|
||||
"""Convert a integer to hexadecimal string."""
|
||||
# strip 'L' for python 2
|
||||
return hex(n)[2:].rjust(6, "0").upper().rstrip("L")
|
||||
|
||||
|
||||
def bin2int(binstr):
|
||||
def bin2int(binstr: str) -> int:
|
||||
"""Convert a binary string to integer."""
|
||||
return int(binstr, 2)
|
||||
|
||||
|
||||
def bin2hex(hexstr):
|
||||
"""Convert a hexdecimal string to integer."""
|
||||
return int2hex(bin2int(hexstr))
|
||||
def bin2hex(binstr: str) -> str:
|
||||
"""Convert a binary string to hexadecimal string."""
|
||||
return "{0:X}".format(int(binstr, 2))
|
||||
|
||||
|
||||
def bin2np(binstr):
|
||||
"""Convert a binary string to numpy array."""
|
||||
return np.array([int(i) for i in binstr])
|
||||
def df(msg: str) -> int:
|
||||
"""Decode Downlink Format value, bits 1 to 5."""
|
||||
dfbin = hex2bin(msg[:2])
|
||||
return min(bin2int(dfbin[0:5]), 24)
|
||||
|
||||
|
||||
def np2bin(npbin):
|
||||
"""Convert a binary numpy array to string."""
|
||||
return np.array2string(npbin, separator="")[1:-1]
|
||||
|
||||
|
||||
def df(msg):
|
||||
"""Decode Downlink Format vaule, bits 1 to 5."""
|
||||
msgbin = hex2bin(msg)
|
||||
return min(bin2int(msgbin[0:5]), 24)
|
||||
|
||||
|
||||
def crc(msg, encode=False):
|
||||
def crc(msg: str, encode: bool = False) -> int:
|
||||
"""Mode-S Cyclic Redundancy Check.
|
||||
|
||||
Detect if bit error occurs in the Mode-S message. When encode option is on,
|
||||
the checksum is generated.
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
encode (bool): True to encode the date only and return the checksum
|
||||
msg: 28 bytes hexadecimal message string
|
||||
encode: True to encode the date only and return the checksum
|
||||
Returns:
|
||||
int: message checksum, or partity bits (encoder)
|
||||
|
||||
@@ -92,7 +77,7 @@ def crc(msg, encode=False):
|
||||
return result
|
||||
|
||||
|
||||
def crc_legacy(msg, encode=False):
|
||||
def crc_legacy(msg: str, encode: bool = False) -> int:
|
||||
"""Mode-S Cyclic Redundancy Check. (Legacy code, 2x slow)."""
|
||||
# the polynominal generattor code for CRC [1111111111111010000001001]
|
||||
generator = np.array(
|
||||
@@ -100,7 +85,7 @@ def crc_legacy(msg, encode=False):
|
||||
)
|
||||
ng = len(generator)
|
||||
|
||||
msgnpbin = bin2np(hex2bin(msg))
|
||||
msgnpbin = np.array([int(i) for i in hex2bin(msg)])
|
||||
|
||||
if encode:
|
||||
msgnpbin[-24:] = [0] * 24
|
||||
@@ -114,11 +99,13 @@ def crc_legacy(msg, encode=False):
|
||||
msgnpbin[i : i + ng] = np.bitwise_xor(msgnpbin[i : i + ng], generator)
|
||||
|
||||
# last 24 bits
|
||||
reminder = bin2int(np2bin(msgnpbin[-24:]))
|
||||
msgbin = np.array2string(msgnpbin[-24:], separator="")[1:-1]
|
||||
reminder = bin2int(msgbin)
|
||||
|
||||
return reminder
|
||||
|
||||
|
||||
def floor(x):
|
||||
def floor(x: float) -> int:
|
||||
"""Mode-S floor function.
|
||||
|
||||
Defined as the greatest integer value k, such that k <= x
|
||||
@@ -128,7 +115,7 @@ def floor(x):
|
||||
return int(np.floor(x))
|
||||
|
||||
|
||||
def icao(msg):
|
||||
def icao(msg: str) -> Optional[str]:
|
||||
"""Calculate the ICAO address from an Mode-S message.
|
||||
|
||||
Applicable only with DF4, DF5, DF20, DF21 messages.
|
||||
@@ -140,13 +127,14 @@ def icao(msg):
|
||||
String: ICAO address in 6 bytes hexadecimal string
|
||||
|
||||
"""
|
||||
addr: Optional[str]
|
||||
DF = df(msg)
|
||||
|
||||
if DF in (11, 17, 18):
|
||||
addr = msg[2:8]
|
||||
elif DF in (0, 4, 5, 16, 20, 21):
|
||||
c0 = crc(msg, encode=True)
|
||||
c1 = hex2int(msg[-6:])
|
||||
c1 = int(msg[-6:], 16)
|
||||
addr = "%06X" % (c0 ^ c1)
|
||||
else:
|
||||
addr = None
|
||||
@@ -154,12 +142,12 @@ def icao(msg):
|
||||
return addr
|
||||
|
||||
|
||||
def is_icao_assigned(icao):
|
||||
def is_icao_assigned(icao: str) -> bool:
|
||||
"""Check whether the ICAO address is assigned (Annex 10, Vol 3)."""
|
||||
if (icao is None) or (not isinstance(icao, str)) or (len(icao) != 6):
|
||||
return False
|
||||
|
||||
icaoint = hex2int(icao)
|
||||
icaoint = int(icao, 16)
|
||||
|
||||
if 0x200000 < icaoint < 0x27FFFF:
|
||||
return False # AFI
|
||||
@@ -183,7 +171,7 @@ def is_icao_assigned(icao):
|
||||
return True
|
||||
|
||||
|
||||
def typecode(msg):
|
||||
def typecode(msg: str) -> Optional[int]:
|
||||
"""Type code of ADS-B message
|
||||
|
||||
Args:
|
||||
@@ -195,35 +183,30 @@ def typecode(msg):
|
||||
if df(msg) not in (17, 18):
|
||||
return None
|
||||
|
||||
msgbin = hex2bin(msg)
|
||||
return bin2int(msgbin[32:37])
|
||||
tcbin = hex2bin(msg[8:10])
|
||||
return bin2int(tcbin[0:5])
|
||||
|
||||
|
||||
def cprNL(lat):
|
||||
def cprNL(lat: float) -> int:
|
||||
"""NL() function in CPR decoding."""
|
||||
|
||||
if lat == 0:
|
||||
if np.isclose(lat, 0):
|
||||
return 59
|
||||
|
||||
if lat == 87 or lat == -87:
|
||||
elif np.isclose(abs(lat), 87):
|
||||
return 2
|
||||
|
||||
if lat > 87 or lat < -87:
|
||||
elif lat > 87 or lat < -87:
|
||||
return 1
|
||||
|
||||
nz = 15
|
||||
a = 1 - np.cos(np.pi / (2 * nz))
|
||||
b = np.cos(np.pi / 180.0 * abs(lat)) ** 2
|
||||
b = np.cos(np.pi / 180 * abs(lat)) ** 2
|
||||
nl = 2 * np.pi / (np.arccos(1 - a / b))
|
||||
NL = floor(nl)
|
||||
return NL
|
||||
|
||||
|
||||
def idcode(msg):
|
||||
"""Compute identity (squawk code).
|
||||
|
||||
Applicable only for DF5 or DF21 messages, bit 20-32.
|
||||
credit: @fbyrkjeland
|
||||
def idcode(msg: str) -> str:
|
||||
"""Compute identity code (squawk) encoded in DF5 or DF21 message.
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
@@ -236,20 +219,37 @@ def idcode(msg):
|
||||
raise RuntimeError("Message must be Downlink Format 5 or 21.")
|
||||
|
||||
mbin = hex2bin(msg)
|
||||
idcodebin = mbin[19:32]
|
||||
|
||||
C1 = mbin[19]
|
||||
A1 = mbin[20]
|
||||
C2 = mbin[21]
|
||||
A2 = mbin[22]
|
||||
C4 = mbin[23]
|
||||
A4 = mbin[24]
|
||||
# _ = mbin[25]
|
||||
B1 = mbin[26]
|
||||
D1 = mbin[27]
|
||||
B2 = mbin[28]
|
||||
D2 = mbin[29]
|
||||
B4 = mbin[30]
|
||||
D4 = mbin[31]
|
||||
return squawk(idcodebin)
|
||||
|
||||
|
||||
def squawk(binstr: str) -> str:
|
||||
"""Decode 13 bits identity (squawk) code.
|
||||
|
||||
Args:
|
||||
binstr (String): 13 bits binary string
|
||||
|
||||
Returns:
|
||||
string: squawk code
|
||||
|
||||
"""
|
||||
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)
|
||||
byte2 = int(B4 + B2 + B1, 2)
|
||||
@@ -259,11 +259,8 @@ def idcode(msg):
|
||||
return str(byte1) + str(byte2) + str(byte3) + str(byte4)
|
||||
|
||||
|
||||
def altcode(msg):
|
||||
"""Compute the altitude.
|
||||
|
||||
Applicable only for DF4 or DF20 message, bit 20-32.
|
||||
credit: @fbyrkjeland
|
||||
def altcode(msg: str) -> Optional[int]:
|
||||
"""Compute altitude encoded in DF4 or DF20 message.
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
@@ -272,50 +269,78 @@ def altcode(msg):
|
||||
int: altitude in ft
|
||||
|
||||
"""
|
||||
alt: Optional[int]
|
||||
|
||||
if df(msg) not in [0, 4, 16, 20]:
|
||||
raise RuntimeError("Message must be Downlink Format 0, 4, 16, or 20.")
|
||||
|
||||
# Altitude code, bit 20-32
|
||||
mbin = hex2bin(msg)
|
||||
|
||||
mbit = mbin[25] # M bit: 26
|
||||
qbit = mbin[27] # Q bit: 28
|
||||
altitude_code = mbin[19:32]
|
||||
|
||||
if mbit == "0": # unit in ft
|
||||
if qbit == "1": # 25ft interval
|
||||
vbin = mbin[19:25] + mbin[26] + mbin[28:32]
|
||||
alt = altitude(altitude_code)
|
||||
|
||||
return alt
|
||||
|
||||
|
||||
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
|
||||
if qbit == "0": # 100ft interval, above 50175ft
|
||||
C1 = mbin[19]
|
||||
A1 = mbin[20]
|
||||
C2 = mbin[21]
|
||||
A2 = mbin[22]
|
||||
C4 = mbin[23]
|
||||
A4 = mbin[24]
|
||||
# _ = mbin[25]
|
||||
B1 = mbin[26]
|
||||
# D1 = mbin[27] # always zero
|
||||
B2 = mbin[28]
|
||||
D2 = mbin[29]
|
||||
B4 = mbin[30]
|
||||
D4 = mbin[31]
|
||||
if Qbit == "0": # 100ft interval, above 50187.5ft
|
||||
C1 = binstr[0]
|
||||
A1 = binstr[1]
|
||||
C2 = binstr[2]
|
||||
A2 = binstr[3]
|
||||
C4 = binstr[4]
|
||||
A4 = binstr[5]
|
||||
# M = binstr[6]
|
||||
B1 = binstr[7]
|
||||
# Q = binstr[8]
|
||||
B2 = binstr[9]
|
||||
D2 = binstr[10]
|
||||
B4 = binstr[11]
|
||||
D4 = binstr[12]
|
||||
|
||||
graystr = D2 + D4 + A1 + A2 + A4 + B1 + B2 + B4 + C1 + C2 + C4
|
||||
alt = gray2alt(graystr)
|
||||
|
||||
if mbit == "1": # unit in meter
|
||||
vbin = mbin[19:25] + mbin[26:31]
|
||||
if Mbit == "1": # unit in meter
|
||||
vbin = binstr[:6] + binstr[7:]
|
||||
alt = int(bin2int(vbin) * 3.28084) # convert to ft
|
||||
|
||||
return alt
|
||||
|
||||
|
||||
def gray2alt(codestr):
|
||||
gc500 = codestr[:8]
|
||||
def gray2alt(binstr: str) -> Optional[int]:
|
||||
gc500 = binstr[:8]
|
||||
n500 = gray2int(gc500)
|
||||
|
||||
# in 100-ft step must be converted first
|
||||
gc100 = codestr[8:]
|
||||
gc100 = binstr[8:]
|
||||
n100 = gray2int(gc100)
|
||||
|
||||
if n100 in [0, 5, 6]:
|
||||
@@ -331,9 +356,9 @@ def gray2alt(codestr):
|
||||
return alt
|
||||
|
||||
|
||||
def gray2int(graystr):
|
||||
def gray2int(binstr: str) -> int:
|
||||
"""Convert greycode to binary."""
|
||||
num = bin2int(graystr)
|
||||
num = bin2int(binstr)
|
||||
num ^= num >> 8
|
||||
num ^= num >> 4
|
||||
num ^= num >> 2
|
||||
@@ -341,12 +366,12 @@ def gray2int(graystr):
|
||||
return num
|
||||
|
||||
|
||||
def data(msg):
|
||||
def data(msg: str) -> str:
|
||||
"""Return the data frame in the message, bytes 9 to 22."""
|
||||
return msg[8:-6]
|
||||
|
||||
|
||||
def allzeros(msg):
|
||||
def allzeros(msg: str) -> bool:
|
||||
"""Check if the data bits are all zeros.
|
||||
|
||||
Args:
|
||||
@@ -364,7 +389,7 @@ def allzeros(msg):
|
||||
return True
|
||||
|
||||
|
||||
def wrongstatus(data, sb, msb, lsb):
|
||||
def wrongstatus(data: str, sb: int, msb: int, lsb: int) -> bool:
|
||||
"""Check if the status bit and field bits are consistency.
|
||||
|
||||
This Function is used for checking BDS code versions.
|
||||
@@ -379,3 +404,85 @@ def wrongstatus(data, sb, msb, lsb):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def fs(msg):
|
||||
"""Decode flight status for DF 4, 5, 20, and 21.
|
||||
|
||||
Args:
|
||||
msg (str): 14 hexdigits string
|
||||
Returns:
|
||||
int, str: flight status, description
|
||||
|
||||
"""
|
||||
msgbin = hex2bin(msg)
|
||||
fs = 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
|
||||
|
||||
|
||||
def dr(msg):
|
||||
"""Decode downlink request for DF 4, 5, 20, and 21.
|
||||
|
||||
Args:
|
||||
msg (str): 14 hexdigits string
|
||||
Returns:
|
||||
int, str: downlink request, description
|
||||
|
||||
"""
|
||||
msgbin = hex2bin(msg)
|
||||
dr = 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
|
||||
|
||||
|
||||
def um(msg):
|
||||
"""Decode utility message for DF 4, 5, 20, and 21.
|
||||
|
||||
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 = hex2bin(msg)
|
||||
iis = bin2int(msgbin[13:17])
|
||||
ids = 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
|
||||
@@ -1,6 +1,6 @@
|
||||
from __future__ import absolute_import, print_function, division
|
||||
import os
|
||||
import time
|
||||
import traceback
|
||||
import datetime
|
||||
import csv
|
||||
import pyModeS as pms
|
||||
@@ -27,7 +27,7 @@ class Decode:
|
||||
self.dumpto = None
|
||||
|
||||
def process_raw(self, adsb_ts, adsb_msg, commb_ts, commb_msg, tnow=None):
|
||||
"""process a chunk of adsb and commb messages recieved in the same
|
||||
"""process a chunk of adsb and commb messages received in the same
|
||||
time period.
|
||||
"""
|
||||
if tnow is None:
|
||||
@@ -232,10 +232,13 @@ class Decode:
|
||||
self.acs[icao]["t60"] = t
|
||||
if ias60:
|
||||
self.acs[icao]["ias"] = ias60
|
||||
output_buffer.append([t, icao, "ias60", ias60])
|
||||
if hdg60:
|
||||
self.acs[icao]["hdg"] = hdg60
|
||||
output_buffer.append([t, icao, "hdg60", hdg60])
|
||||
if mach60:
|
||||
self.acs[icao]["mach"] = mach60
|
||||
output_buffer.append([t, icao, "mach60", mach60])
|
||||
|
||||
if roc60baro:
|
||||
output_buffer.append([t, icao, "roc60baro", roc60baro])
|
||||
@@ -259,26 +262,31 @@ class Decode:
|
||||
return
|
||||
|
||||
def get_aircraft(self):
|
||||
"""all aircraft that are stored in memeory"""
|
||||
"""all aircraft that are stored in memory"""
|
||||
acs = self.acs
|
||||
return acs
|
||||
|
||||
def run(self, raw_pipe_out, ac_pipe_in):
|
||||
def run(self, raw_pipe_out, ac_pipe_in, exception_queue):
|
||||
local_buffer = []
|
||||
while True:
|
||||
while raw_pipe_out.poll():
|
||||
data = raw_pipe_out.recv()
|
||||
local_buffer.append(data)
|
||||
try:
|
||||
while raw_pipe_out.poll():
|
||||
data = raw_pipe_out.recv()
|
||||
local_buffer.append(data)
|
||||
|
||||
for data in local_buffer:
|
||||
self.process_raw(
|
||||
data["adsb_ts"],
|
||||
data["adsb_msg"],
|
||||
data["commb_ts"],
|
||||
data["commb_msg"],
|
||||
)
|
||||
local_buffer = []
|
||||
for data in local_buffer:
|
||||
self.process_raw(
|
||||
data["adsb_ts"],
|
||||
data["adsb_msg"],
|
||||
data["commb_ts"],
|
||||
data["commb_msg"],
|
||||
)
|
||||
local_buffer = []
|
||||
|
||||
acs = self.get_aircraft()
|
||||
ac_pipe_in.send(acs)
|
||||
time.sleep(0.001)
|
||||
acs = self.get_aircraft()
|
||||
ac_pipe_in.send(acs)
|
||||
time.sleep(0.001)
|
||||
|
||||
except Exception as e:
|
||||
tb = traceback.format_exc()
|
||||
exception_queue.put((e, tb))
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from __future__ import print_function, division
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import curses
|
||||
import signal
|
||||
import multiprocessing
|
||||
from pyModeS.streamer.decode import Decode
|
||||
from pyModeS.streamer.screen import Screen
|
||||
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"]
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--source",
|
||||
help='Choose data source, "rtlsdr" or "net"',
|
||||
required=True,
|
||||
default="net",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--connect",
|
||||
help="Define server, port and data type. Supported data types are: %s"
|
||||
% support_rawtypes,
|
||||
nargs=3,
|
||||
metavar=("SERVER", "PORT", "DATATYPE"),
|
||||
default=None,
|
||||
required=False,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--latlon",
|
||||
help="Receiver latitude and longitude, needed for the surface position, default none",
|
||||
nargs=2,
|
||||
metavar=("LAT", "LON"),
|
||||
default=None,
|
||||
required=False,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--show-uncertainty",
|
||||
dest="uncertainty",
|
||||
help="Display uncertainty values, default off",
|
||||
action="store_true",
|
||||
required=False,
|
||||
default=False,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dumpto",
|
||||
help="Folder to dump decoded output, default none",
|
||||
required=False,
|
||||
default=None,
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
SOURCE = args.source
|
||||
LATLON = args.latlon
|
||||
UNCERTAINTY = args.uncertainty
|
||||
DUMPTO = args.dumpto
|
||||
|
||||
if SOURCE == "rtlsdr":
|
||||
pass
|
||||
elif SOURCE == "net":
|
||||
if args.connect is None:
|
||||
print("Error: --connect argument must not be empty.")
|
||||
else:
|
||||
SERVER, PORT, DATATYPE = args.connect
|
||||
if DATATYPE not in support_rawtypes:
|
||||
print("Data type not supported, avaiable ones are %s" % support_rawtypes)
|
||||
|
||||
else:
|
||||
print('Source must be "rtlsdr" or "net".')
|
||||
sys.exit(1)
|
||||
|
||||
if DUMPTO is not None:
|
||||
# append to current folder except root is given
|
||||
if DUMPTO[0] != "/":
|
||||
DUMPTO = os.getcwd() + "/" + DUMPTO
|
||||
|
||||
if not os.path.isdir(DUMPTO):
|
||||
print("Error: dump folder (%s) does not exist" % DUMPTO)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# raw_event = multiprocessing.Event()
|
||||
# ac_event = multiprocessing.Event()
|
||||
# raw_queue = multiprocessing.Queue()
|
||||
# ac_queue = multiprocessing.Queue()
|
||||
|
||||
raw_pipe_in, raw_pipe_out = multiprocessing.Pipe()
|
||||
ac_pipe_in, ac_pipe_out = multiprocessing.Pipe()
|
||||
stop_flag = multiprocessing.Value("b", False)
|
||||
|
||||
if SOURCE == "net":
|
||||
source = NetSource(host=SERVER, port=PORT, rawtype=DATATYPE)
|
||||
elif SOURCE == "rtlsdr":
|
||||
source = RtlSdrSource()
|
||||
|
||||
|
||||
recv_process = multiprocessing.Process(target=source.run, args=(raw_pipe_in, stop_flag))
|
||||
|
||||
|
||||
decode = Decode(latlon=LATLON, dumpto=DUMPTO)
|
||||
decode_process = multiprocessing.Process(
|
||||
target=decode.run, args=(raw_pipe_out, ac_pipe_in)
|
||||
)
|
||||
|
||||
screen = Screen(uncertainty=UNCERTAINTY)
|
||||
screen_process = multiprocessing.Process(target=screen.run, args=(ac_pipe_out,))
|
||||
|
||||
|
||||
def closeall(signal, frame):
|
||||
print("KeyboardInterrupt (ID: {}). Cleaning up...".format(signal))
|
||||
stop_flag.value = True
|
||||
curses.endwin()
|
||||
recv_process.terminate()
|
||||
decode_process.terminate()
|
||||
screen_process.terminate()
|
||||
recv_process.join()
|
||||
decode_process.join()
|
||||
screen_process.join()
|
||||
exit(0)
|
||||
|
||||
|
||||
signal.signal(signal.SIGINT, closeall)
|
||||
|
||||
recv_process.start()
|
||||
decode_process.start()
|
||||
screen_process.start()
|
||||
155
pyModeS/streamer/modeslive.py
Executable file
155
pyModeS/streamer/modeslive.py
Executable file
@@ -0,0 +1,155 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import argparse
|
||||
import curses
|
||||
import signal
|
||||
import multiprocessing
|
||||
from pyModeS.streamer.decode import Decode
|
||||
from pyModeS.streamer.screen import Screen
|
||||
from pyModeS.streamer.source import NetSource, RtlSdrSource # , RtlSdrSource24
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
support_rawtypes = ["raw", "beast", "skysense"]
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--source",
|
||||
help='Choose data source, "rtlsdr", "rtlsdr24" or "net"',
|
||||
required=True,
|
||||
default="net",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--connect",
|
||||
help="Define server, port and data type. Supported data types are: {}".format(
|
||||
support_rawtypes
|
||||
),
|
||||
nargs=3,
|
||||
metavar=("SERVER", "PORT", "DATATYPE"),
|
||||
default=None,
|
||||
required=False,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--latlon",
|
||||
help="Receiver latitude and longitude, needed for the surface position, default none",
|
||||
nargs=2,
|
||||
metavar=("LAT", "LON"),
|
||||
default=None,
|
||||
required=False,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--show-uncertainty",
|
||||
dest="uncertainty",
|
||||
help="Display uncertainty values, default off",
|
||||
action="store_true",
|
||||
required=False,
|
||||
default=False,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dumpto",
|
||||
help="Folder to dump decoded output, default none",
|
||||
required=False,
|
||||
default=None,
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
SOURCE = args.source
|
||||
LATLON = args.latlon
|
||||
UNCERTAINTY = args.uncertainty
|
||||
DUMPTO = args.dumpto
|
||||
|
||||
if SOURCE in ["rtlsdr", "rtlsdr24"]:
|
||||
pass
|
||||
elif SOURCE == "net":
|
||||
if args.connect is None:
|
||||
print("Error: --connect argument must not be empty.")
|
||||
else:
|
||||
SERVER, PORT, DATATYPE = args.connect
|
||||
if DATATYPE not in support_rawtypes:
|
||||
print(
|
||||
"Data type not supported, available ones are %s"
|
||||
% support_rawtypes
|
||||
)
|
||||
|
||||
else:
|
||||
print('Source must be "rtlsdr" or "net".')
|
||||
sys.exit(1)
|
||||
|
||||
if DUMPTO is not None:
|
||||
# append to current folder except root is given
|
||||
if DUMPTO[0] != "/":
|
||||
DUMPTO = os.getcwd() + "/" + DUMPTO
|
||||
|
||||
if not os.path.isdir(DUMPTO):
|
||||
print("Error: dump folder (%s) does not exist" % DUMPTO)
|
||||
sys.exit(1)
|
||||
|
||||
# redirect all stdout to null, avoiding messing up with the screen
|
||||
sys.stdout = open(os.devnull, "w")
|
||||
|
||||
raw_pipe_in, raw_pipe_out = multiprocessing.Pipe()
|
||||
ac_pipe_in, ac_pipe_out = multiprocessing.Pipe()
|
||||
exception_queue = multiprocessing.Queue()
|
||||
stop_flag = multiprocessing.Value("b", False)
|
||||
|
||||
if SOURCE == "net":
|
||||
source = NetSource(host=SERVER, port=PORT, rawtype=DATATYPE)
|
||||
elif SOURCE == "rtlsdr":
|
||||
source = RtlSdrSource()
|
||||
# elif SOURCE == "rtlsdr24":
|
||||
# source = RtlSdrSource24()
|
||||
|
||||
recv_process = multiprocessing.Process(
|
||||
target=source.run, args=(raw_pipe_in, stop_flag, exception_queue)
|
||||
)
|
||||
|
||||
decode = Decode(latlon=LATLON, dumpto=DUMPTO)
|
||||
decode_process = multiprocessing.Process(
|
||||
target=decode.run, args=(raw_pipe_out, ac_pipe_in, exception_queue)
|
||||
)
|
||||
|
||||
screen = Screen(uncertainty=UNCERTAINTY)
|
||||
screen_process = multiprocessing.Process(
|
||||
target=screen.run, args=(ac_pipe_out, exception_queue)
|
||||
)
|
||||
|
||||
def shutdown():
|
||||
stop_flag.value = True
|
||||
curses.endwin()
|
||||
sys.stdout = sys.__stdout__
|
||||
recv_process.terminate()
|
||||
decode_process.terminate()
|
||||
screen_process.terminate()
|
||||
recv_process.join()
|
||||
decode_process.join()
|
||||
screen_process.join()
|
||||
|
||||
def closeall(signal, frame):
|
||||
print("KeyboardInterrupt (ID: {}). Cleaning up...".format(signal))
|
||||
shutdown()
|
||||
sys.exit(0)
|
||||
|
||||
signal.signal(signal.SIGINT, closeall)
|
||||
|
||||
recv_process.start()
|
||||
decode_process.start()
|
||||
screen_process.start()
|
||||
|
||||
while True:
|
||||
if (
|
||||
(not recv_process.is_alive())
|
||||
or (not decode_process.is_alive())
|
||||
or (not screen_process.is_alive())
|
||||
):
|
||||
shutdown()
|
||||
while not exception_queue.empty():
|
||||
trackback = exception_queue.get()
|
||||
print(trackback)
|
||||
|
||||
sys.exit(1)
|
||||
|
||||
time.sleep(0.01)
|
||||
@@ -1,8 +1,8 @@
|
||||
from __future__ import print_function, division
|
||||
import curses
|
||||
import numpy as np
|
||||
import time
|
||||
import threading
|
||||
import traceback
|
||||
|
||||
COLUMNS = [
|
||||
("call", 10),
|
||||
@@ -187,24 +187,32 @@ class Screen(object):
|
||||
self.screen.refresh()
|
||||
self.draw_frame()
|
||||
|
||||
def run(self, ac_pipe_out):
|
||||
def run(self, ac_pipe_out, exception_queue):
|
||||
local_buffer = []
|
||||
key_thread = threading.Thread(target=self.kye_handling)
|
||||
key_thread.daemon = True
|
||||
key_thread.start()
|
||||
|
||||
while True:
|
||||
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:
|
||||
# raise RuntimeError("test exception")
|
||||
|
||||
while ac_pipe_out.poll():
|
||||
acs = ac_pipe_out.recv()
|
||||
local_buffer.append(acs)
|
||||
|
||||
for acs in local_buffer:
|
||||
self.update_ac(acs)
|
||||
|
||||
local_buffer = []
|
||||
|
||||
self.update()
|
||||
except:
|
||||
except curses.error:
|
||||
pass
|
||||
except Exception as e:
|
||||
tb = traceback.format_exc()
|
||||
exception_queue.put(tb)
|
||||
time.sleep(0.1)
|
||||
raise e
|
||||
|
||||
time.sleep(0.001)
|
||||
|
||||
65
pyproject.toml
Normal file
65
pyproject.toml
Normal file
@@ -0,0 +1,65 @@
|
||||
[tool.poetry]
|
||||
name = "pyModeS"
|
||||
version = "2.17"
|
||||
description = "Python Mode-S and ADS-B Decoder"
|
||||
authors = ["Junzi Sun <j.sun-1@tudelft.nl>"]
|
||||
license = "GNU GPL v3"
|
||||
readme = "README.rst"
|
||||
classifiers = [
|
||||
"Development Status :: 4 - Beta",
|
||||
"Intended Audience :: Developers",
|
||||
"Topic :: Software Development :: Libraries",
|
||||
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Typing :: Typed",
|
||||
]
|
||||
packages = [
|
||||
{ include = "pyModeS", from = "." },
|
||||
]
|
||||
include = [
|
||||
"LICENSE",
|
||||
"*.pyx",
|
||||
"*.pxd",
|
||||
"*.pyi",
|
||||
"py.typed",
|
||||
{ path = "pyModeS/**/*.so", format = "wheel" },
|
||||
{ path = "pyModeS/**/*.pyd", format = "wheel" }
|
||||
]
|
||||
|
||||
[tool.poetry.build]
|
||||
generate-setup-file = false
|
||||
script = "build.py"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
modeslive = "pyModeS.streamer.modeslive:main"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.8"
|
||||
numpy = "^1.24"
|
||||
pyzmq = "^24.0"
|
||||
pyrtlsdr = {version = "^0.2.93", optional = true}
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
mypy = "^0.991"
|
||||
flake8 = "^5.0.0"
|
||||
black = "^22.12.0"
|
||||
isort = "^5.11.4"
|
||||
pytest = "^7.2.0"
|
||||
pytest-cov = "^4.0.0"
|
||||
codecov = "^2.1.12"
|
||||
|
||||
[tool.poetry.extras]
|
||||
rtlsdr = ["pyrtlsdr"]
|
||||
|
||||
[tool.black]
|
||||
line-length = 80
|
||||
target_version = ['py38', 'py39', 'py310', 'py311']
|
||||
include = '\.pyi?$'
|
||||
|
||||
[tool.isort]
|
||||
line_length = 80
|
||||
profile = "black"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0", "Cython>=0.29.32"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
106
setup.py
106
setup.py
@@ -1,106 +0,0 @@
|
||||
"""A setuptools based setup module.
|
||||
|
||||
See:
|
||||
https://packaging.python.org/en/latest/distributing.html
|
||||
https://github.com/pypa/sampleproject
|
||||
|
||||
Steps for deploying a new verison:
|
||||
1. Increase the version number
|
||||
2. remove the old deployment under [dist] and [build] folder
|
||||
3. run: python setup.py sdist
|
||||
run: python setup.py bdist_wheel --universal
|
||||
4. twine upload dist/*
|
||||
"""
|
||||
|
||||
# Always prefer setuptools over distutils
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
# To use a consistent encoding
|
||||
from codecs import open
|
||||
from os import path
|
||||
|
||||
here = path.abspath(path.dirname(__file__))
|
||||
|
||||
# Get the long description from the README file
|
||||
with open(path.join(here, "README.rst"), encoding="utf-8") as f:
|
||||
long_description = f.read()
|
||||
|
||||
setup(
|
||||
name="pyModeS",
|
||||
# Versions should comply with PEP440. For a discussion on single-sourcing
|
||||
# the version across setup.py and the project code, see
|
||||
# https://packaging.python.org/en/latest/single_source_version.html
|
||||
version="2.4",
|
||||
description="Python Mode-S and ADS-B Decoder",
|
||||
long_description=long_description,
|
||||
# The project's main homepage.
|
||||
url="https://github.com/junzis/pyModeS",
|
||||
# Author details
|
||||
author="Junzi Sun",
|
||||
author_email="j.sun-1@tudelft.nl",
|
||||
# Choose your license
|
||||
license="GNU GPL v3",
|
||||
# See https://pypi.python.org/pypi?%3Aaction=list_classifiers
|
||||
classifiers=[
|
||||
# How mature is this project? Common values are
|
||||
# 3 - Alpha
|
||||
# 4 - Beta
|
||||
# 5 - Production/Stable
|
||||
"Development Status :: 4 - Beta",
|
||||
# Indicate who your project is intended for
|
||||
"Intended Audience :: Developers",
|
||||
"Topic :: Software Development :: Build Tools",
|
||||
# Pick your license as you wish (should match "license" above)
|
||||
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
||||
# Specify the Python versions you support here. In particular, ensure
|
||||
# that you indicate whether you support Python 2, Python 3 or both.
|
||||
"Programming Language :: Python :: 2",
|
||||
"Programming Language :: Python :: 2.7",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.3",
|
||||
"Programming Language :: Python :: 3.4",
|
||||
"Programming Language :: Python :: 3.5",
|
||||
"Programming Language :: Python :: 3.6",
|
||||
],
|
||||
# What does your project relate to?
|
||||
keywords="Mode-S ADS-B EHS decoding",
|
||||
# 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"]),
|
||||
# Alternatively, if you want to distribute just a my_module.py, uncomment
|
||||
# this:
|
||||
# py_modules=["my_module"],
|
||||
# List run-time dependencies here. These will be installed by pip when
|
||||
# your project is installed. For an analysis of "install_requires" vs pip's
|
||||
# requirements files see:
|
||||
# https://packaging.python.org/en/latest/requirements.html
|
||||
install_requires=["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"],
|
||||
)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user