Compare commits
152 Commits
adsb_encod
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
b4be55116c | ||
|
fdfa2b3fab | ||
|
2f5dbdf08d | ||
|
b22734ad51 | ||
|
e2de7bc9ac | ||
|
74ea61fb11 | ||
|
08218f558d | ||
|
8acbdf186c | ||
|
d8dd978730 | ||
|
01ceb8114a | ||
|
4a7c719978 | ||
|
93794b64ca | ||
|
9646c2fbea | ||
|
97a50a5b13 | ||
|
81b54accb5 | ||
|
942c68dc78 | ||
|
36fb0de92c | ||
|
582a0d2188 | ||
|
45ae55d965 | ||
|
4cf024f144 | ||
|
eea09227b0 | ||
|
852315ed2d | ||
|
77273153cb | ||
|
b4c81db151 | ||
|
b6608b2f78 | ||
|
070dc80bf4 | ||
|
8d9a8df9d9 | ||
|
458a78c30b | ||
|
57aa7a7d9c | ||
|
b1f8f6ed62 | ||
|
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 | ||
|
dc8f194e6e |
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"
|
16
.github/workflows/build-publish.yml
vendored
Normal file
16
.github/workflows/build-publish.yml
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
name: build and publish
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build and publish to pypi
|
||||
uses: JRubics/poetry-publish@v2.0
|
||||
with:
|
||||
poetry_version: "==1.8.2"
|
||||
pypi_token: ${{ secrets.PYPI_API_TOKEN_PYMODES }}
|
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.6.1"
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
python-version: ["3.9", "3.10", "3.11", "3.12"]
|
||||
|
||||
env:
|
||||
PYTHON_VERSION: ${{ matrix.python-version }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
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@v4
|
||||
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.4.0
|
||||
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@v4
|
||||
with:
|
||||
env_vars: PYTHON_VERSION
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -5,9 +5,6 @@ __pycache__/
|
||||
*.py[cod]
|
||||
.pytest_cache/
|
||||
|
||||
#cython
|
||||
*.c
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
|
11
Makefile
11
Makefile
@ -8,11 +8,18 @@ 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/decoder -type f -name '*.c' -delete
|
||||
find pyModeS/decoder -type f -name '*.so' -delete
|
||||
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
|
||||
|
44
README.rst
44
README.rst
@ -1,7 +1,7 @@
|
||||
The Python ADS-B/Mode-S Decoder
|
||||
===============================
|
||||
|
||||
PyModeS is a Python library designed to decode Mode-S (including ADS-B) message. It can be imported to your python project or used as a standalone tool to view and save live traffic data.
|
||||
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.
|
||||
|
||||
@ -72,21 +72,35 @@ Installation examples::
|
||||
# stable version
|
||||
pip install pyModeS
|
||||
|
||||
# conda (compiled) version
|
||||
conda install -c conda-forge pymodes
|
||||
|
||||
# development version
|
||||
pip install git+https://github.com/junzis/pyModeS
|
||||
|
||||
|
||||
Dependencies ``numpy``, ``pyzmq`` and ``pyrtlsdr`` are installed automatically during previous installations processes.
|
||||
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
|
||||
make ext
|
||||
make install
|
||||
poetry install -E rtlsdr
|
||||
|
||||
|
||||
View live traffic (modeslive)
|
||||
@ -112,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
|
||||
|
||||
@ -261,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
|
||||
|
||||
@ -273,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
|
||||
|
||||
|
67
build.py
Normal file
67
build.py
Normal file
@ -0,0 +1,67 @@
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
# import pip
|
||||
from Cython.Build import cythonize
|
||||
from setuptools import Distribution, Extension
|
||||
from setuptools.command import build_ext
|
||||
|
||||
|
||||
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_output_mapping():
|
||||
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()
|
881
poetry.lock
generated
Normal file
881
poetry.lock
generated
Normal file
@ -0,0 +1,881 @@
|
||||
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "black"
|
||||
version = "24.8.0"
|
||||
description = "The uncompromising code formatter."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6"},
|
||||
{file = "black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb"},
|
||||
{file = "black-24.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:707a1ca89221bc8a1a64fb5e15ef39cd755633daa672a9db7498d1c19de66a42"},
|
||||
{file = "black-24.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a"},
|
||||
{file = "black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1"},
|
||||
{file = "black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af"},
|
||||
{file = "black-24.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4"},
|
||||
{file = "black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af"},
|
||||
{file = "black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368"},
|
||||
{file = "black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed"},
|
||||
{file = "black-24.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018"},
|
||||
{file = "black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2"},
|
||||
{file = "black-24.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:505289f17ceda596658ae81b61ebbe2d9b25aa78067035184ed0a9d855d18afd"},
|
||||
{file = "black-24.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b19c9ad992c7883ad84c9b22aaa73562a16b819c1d8db7a1a1a49fb7ec13c7d2"},
|
||||
{file = "black-24.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f13f7f386f86f8121d76599114bb8c17b69d962137fc70efe56137727c7047e"},
|
||||
{file = "black-24.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:f490dbd59680d809ca31efdae20e634f3fae27fba3ce0ba3208333b713bc3920"},
|
||||
{file = "black-24.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c"},
|
||||
{file = "black-24.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e"},
|
||||
{file = "black-24.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e84e33b37be070ba135176c123ae52a51f82306def9f7d063ee302ecab2cf47"},
|
||||
{file = "black-24.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb"},
|
||||
{file = "black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed"},
|
||||
{file = "black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=8.0.0"
|
||||
mypy-extensions = ">=0.4.3"
|
||||
packaging = ">=22.0"
|
||||
pathspec = ">=0.9.0"
|
||||
platformdirs = ">=2"
|
||||
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
|
||||
typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""}
|
||||
|
||||
[package.extras]
|
||||
colorama = ["colorama (>=0.4.3)"]
|
||||
d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"]
|
||||
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
|
||||
uvloop = ["uvloop (>=0.15.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2024.7.4"
|
||||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"},
|
||||
{file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cffi"
|
||||
version = "1.17.0"
|
||||
description = "Foreign Function Interface for Python calling C code."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "cffi-1.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f9338cc05451f1942d0d8203ec2c346c830f8e86469903d5126c1f0a13a2bcbb"},
|
||||
{file = "cffi-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0ce71725cacc9ebf839630772b07eeec220cbb5f03be1399e0457a1464f8e1a"},
|
||||
{file = "cffi-1.17.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c815270206f983309915a6844fe994b2fa47e5d05c4c4cef267c3b30e34dbe42"},
|
||||
{file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6bdcd415ba87846fd317bee0774e412e8792832e7805938987e4ede1d13046d"},
|
||||
{file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a98748ed1a1df4ee1d6f927e151ed6c1a09d5ec21684de879c7ea6aa96f58f2"},
|
||||
{file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a048d4f6630113e54bb4b77e315e1ba32a5a31512c31a273807d0027a7e69ab"},
|
||||
{file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24aa705a5f5bd3a8bcfa4d123f03413de5d86e497435693b638cbffb7d5d8a1b"},
|
||||
{file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:856bf0924d24e7f93b8aee12a3a1095c34085600aa805693fb7f5d1962393206"},
|
||||
{file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:4304d4416ff032ed50ad6bb87416d802e67139e31c0bde4628f36a47a3164bfa"},
|
||||
{file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:331ad15c39c9fe9186ceaf87203a9ecf5ae0ba2538c9e898e3a6967e8ad3db6f"},
|
||||
{file = "cffi-1.17.0-cp310-cp310-win32.whl", hash = "sha256:669b29a9eca6146465cc574659058ed949748f0809a2582d1f1a324eb91054dc"},
|
||||
{file = "cffi-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:48b389b1fd5144603d61d752afd7167dfd205973a43151ae5045b35793232aa2"},
|
||||
{file = "cffi-1.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c5d97162c196ce54af6700949ddf9409e9833ef1003b4741c2b39ef46f1d9720"},
|
||||
{file = "cffi-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ba5c243f4004c750836f81606a9fcb7841f8874ad8f3bf204ff5e56332b72b9"},
|
||||
{file = "cffi-1.17.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb9333f58fc3a2296fb1d54576138d4cf5d496a2cc118422bd77835e6ae0b9cb"},
|
||||
{file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:435a22d00ec7d7ea533db494da8581b05977f9c37338c80bc86314bec2619424"},
|
||||
{file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1df34588123fcc88c872f5acb6f74ae59e9d182a2707097f9e28275ec26a12d"},
|
||||
{file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df8bb0010fdd0a743b7542589223a2816bdde4d94bb5ad67884348fa2c1c67e8"},
|
||||
{file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8b5b9712783415695663bd463990e2f00c6750562e6ad1d28e072a611c5f2a6"},
|
||||
{file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ffef8fd58a36fb5f1196919638f73dd3ae0db1a878982b27a9a5a176ede4ba91"},
|
||||
{file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e67d26532bfd8b7f7c05d5a766d6f437b362c1bf203a3a5ce3593a645e870b8"},
|
||||
{file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45f7cd36186db767d803b1473b3c659d57a23b5fa491ad83c6d40f2af58e4dbb"},
|
||||
{file = "cffi-1.17.0-cp311-cp311-win32.whl", hash = "sha256:a9015f5b8af1bb6837a3fcb0cdf3b874fe3385ff6274e8b7925d81ccaec3c5c9"},
|
||||
{file = "cffi-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:b50aaac7d05c2c26dfd50c3321199f019ba76bb650e346a6ef3616306eed67b0"},
|
||||
{file = "cffi-1.17.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aec510255ce690d240f7cb23d7114f6b351c733a74c279a84def763660a2c3bc"},
|
||||
{file = "cffi-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2770bb0d5e3cc0e31e7318db06efcbcdb7b31bcb1a70086d3177692a02256f59"},
|
||||
{file = "cffi-1.17.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db9a30ec064129d605d0f1aedc93e00894b9334ec74ba9c6bdd08147434b33eb"},
|
||||
{file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a47eef975d2b8b721775a0fa286f50eab535b9d56c70a6e62842134cf7841195"},
|
||||
{file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3e0992f23bbb0be00a921eae5363329253c3b86287db27092461c887b791e5e"},
|
||||
{file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6107e445faf057c118d5050560695e46d272e5301feffda3c41849641222a828"},
|
||||
{file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb862356ee9391dc5a0b3cbc00f416b48c1b9a52d252d898e5b7696a5f9fe150"},
|
||||
{file = "cffi-1.17.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c1c13185b90bbd3f8b5963cd8ce7ad4ff441924c31e23c975cb150e27c2bf67a"},
|
||||
{file = "cffi-1.17.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:17c6d6d3260c7f2d94f657e6872591fe8733872a86ed1345bda872cfc8c74885"},
|
||||
{file = "cffi-1.17.0-cp312-cp312-win32.whl", hash = "sha256:c3b8bd3133cd50f6b637bb4322822c94c5ce4bf0d724ed5ae70afce62187c492"},
|
||||
{file = "cffi-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:dca802c8db0720ce1c49cce1149ff7b06e91ba15fa84b1d59144fef1a1bc7ac2"},
|
||||
{file = "cffi-1.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6ce01337d23884b21c03869d2f68c5523d43174d4fc405490eb0091057943118"},
|
||||
{file = "cffi-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cab2eba3830bf4f6d91e2d6718e0e1c14a2f5ad1af68a89d24ace0c6b17cced7"},
|
||||
{file = "cffi-1.17.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14b9cbc8f7ac98a739558eb86fabc283d4d564dafed50216e7f7ee62d0d25377"},
|
||||
{file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b00e7bcd71caa0282cbe3c90966f738e2db91e64092a877c3ff7f19a1628fdcb"},
|
||||
{file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41f4915e09218744d8bae14759f983e466ab69b178de38066f7579892ff2a555"},
|
||||
{file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4760a68cab57bfaa628938e9c2971137e05ce48e762a9cb53b76c9b569f1204"},
|
||||
{file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:011aff3524d578a9412c8b3cfaa50f2c0bd78e03eb7af7aa5e0df59b158efb2f"},
|
||||
{file = "cffi-1.17.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:a003ac9edc22d99ae1286b0875c460351f4e101f8c9d9d2576e78d7e048f64e0"},
|
||||
{file = "cffi-1.17.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ef9528915df81b8f4c7612b19b8628214c65c9b7f74db2e34a646a0a2a0da2d4"},
|
||||
{file = "cffi-1.17.0-cp313-cp313-win32.whl", hash = "sha256:70d2aa9fb00cf52034feac4b913181a6e10356019b18ef89bc7c12a283bf5f5a"},
|
||||
{file = "cffi-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:b7b6ea9e36d32582cda3465f54c4b454f62f23cb083ebc7a94e2ca6ef011c3a7"},
|
||||
{file = "cffi-1.17.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:964823b2fc77b55355999ade496c54dde161c621cb1f6eac61dc30ed1b63cd4c"},
|
||||
{file = "cffi-1.17.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:516a405f174fd3b88829eabfe4bb296ac602d6a0f68e0d64d5ac9456194a5b7e"},
|
||||
{file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dec6b307ce928e8e112a6bb9921a1cb00a0e14979bf28b98e084a4b8a742bd9b"},
|
||||
{file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4094c7b464cf0a858e75cd14b03509e84789abf7b79f8537e6a72152109c76e"},
|
||||
{file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2404f3de742f47cb62d023f0ba7c5a916c9c653d5b368cc966382ae4e57da401"},
|
||||
{file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa9d43b02a0c681f0bfbc12d476d47b2b2b6a3f9287f11ee42989a268a1833c"},
|
||||
{file = "cffi-1.17.0-cp38-cp38-win32.whl", hash = "sha256:0bb15e7acf8ab35ca8b24b90af52c8b391690ef5c4aec3d31f38f0d37d2cc499"},
|
||||
{file = "cffi-1.17.0-cp38-cp38-win_amd64.whl", hash = "sha256:93a7350f6706b31f457c1457d3a3259ff9071a66f312ae64dc024f049055f72c"},
|
||||
{file = "cffi-1.17.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a2ddbac59dc3716bc79f27906c010406155031a1c801410f1bafff17ea304d2"},
|
||||
{file = "cffi-1.17.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6327b572f5770293fc062a7ec04160e89741e8552bf1c358d1a23eba68166759"},
|
||||
{file = "cffi-1.17.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbc183e7bef690c9abe5ea67b7b60fdbca81aa8da43468287dae7b5c046107d4"},
|
||||
{file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bdc0f1f610d067c70aa3737ed06e2726fd9d6f7bfee4a351f4c40b6831f4e82"},
|
||||
{file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6d872186c1617d143969defeadac5a904e6e374183e07977eedef9c07c8953bf"},
|
||||
{file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d46ee4764b88b91f16661a8befc6bfb24806d885e27436fdc292ed7e6f6d058"},
|
||||
{file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f76a90c345796c01d85e6332e81cab6d70de83b829cf1d9762d0a3da59c7932"},
|
||||
{file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0e60821d312f99d3e1569202518dddf10ae547e799d75aef3bca3a2d9e8ee693"},
|
||||
{file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:eb09b82377233b902d4c3fbeeb7ad731cdab579c6c6fda1f763cd779139e47c3"},
|
||||
{file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:24658baf6224d8f280e827f0a50c46ad819ec8ba380a42448e24459daf809cf4"},
|
||||
{file = "cffi-1.17.0-cp39-cp39-win32.whl", hash = "sha256:0fdacad9e0d9fc23e519efd5ea24a70348305e8d7d85ecbb1a5fa66dc834e7fb"},
|
||||
{file = "cffi-1.17.0-cp39-cp39-win_amd64.whl", hash = "sha256:7cbc78dc018596315d4e7841c8c3a7ae31cc4d638c9b627f87d52e8abaaf2d29"},
|
||||
{file = "cffi-1.17.0.tar.gz", hash = "sha256:f3157624b7558b914cb039fd1af735e5e8049a87c817cc215109ad1c8779df76"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pycparser = "*"
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "3.3.2"
|
||||
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.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"},
|
||||
{file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"},
|
||||
{file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"},
|
||||
{file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"},
|
||||
{file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"},
|
||||
{file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"},
|
||||
{file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"},
|
||||
{file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"},
|
||||
{file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"},
|
||||
{file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"},
|
||||
{file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"},
|
||||
{file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"},
|
||||
{file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"},
|
||||
{file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"},
|
||||
{file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"},
|
||||
{file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"},
|
||||
{file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"},
|
||||
{file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"},
|
||||
{file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"},
|
||||
{file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"},
|
||||
{file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"},
|
||||
{file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"},
|
||||
{file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"},
|
||||
{file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"},
|
||||
{file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"},
|
||||
{file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"},
|
||||
{file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"},
|
||||
{file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"},
|
||||
{file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"},
|
||||
{file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"},
|
||||
{file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"},
|
||||
{file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"},
|
||||
{file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"},
|
||||
{file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"},
|
||||
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"},
|
||||
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"},
|
||||
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"},
|
||||
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"},
|
||||
{file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"},
|
||||
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"},
|
||||
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"},
|
||||
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"},
|
||||
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"},
|
||||
{file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"},
|
||||
{file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"},
|
||||
{file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"},
|
||||
{file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"},
|
||||
{file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"},
|
||||
{file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"},
|
||||
{file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"},
|
||||
{file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"},
|
||||
{file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"},
|
||||
{file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"},
|
||||
{file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"},
|
||||
{file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"},
|
||||
{file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"},
|
||||
{file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"},
|
||||
{file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"},
|
||||
{file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"},
|
||||
{file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"},
|
||||
{file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"},
|
||||
{file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"},
|
||||
{file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"},
|
||||
{file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"},
|
||||
{file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"},
|
||||
{file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"},
|
||||
{file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"},
|
||||
{file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"},
|
||||
{file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"},
|
||||
{file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"},
|
||||
{file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"},
|
||||
{file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"},
|
||||
{file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"},
|
||||
{file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"},
|
||||
{file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"},
|
||||
{file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"},
|
||||
{file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"},
|
||||
{file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"},
|
||||
{file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"},
|
||||
{file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"},
|
||||
{file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"},
|
||||
{file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"},
|
||||
{file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"},
|
||||
{file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"},
|
||||
{file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"},
|
||||
{file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"},
|
||||
{file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"},
|
||||
{file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"},
|
||||
{file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"},
|
||||
{file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.1.7"
|
||||
description = "Composable command line interface toolkit"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
|
||||
{file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
|
||||
]
|
||||
|
||||
[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.6.1"
|
||||
description = "Code coverage measurement for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"},
|
||||
{file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"},
|
||||
{file = "coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02"},
|
||||
{file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc"},
|
||||
{file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23"},
|
||||
{file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34"},
|
||||
{file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c"},
|
||||
{file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959"},
|
||||
{file = "coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232"},
|
||||
{file = "coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0"},
|
||||
{file = "coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93"},
|
||||
{file = "coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3"},
|
||||
{file = "coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff"},
|
||||
{file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d"},
|
||||
{file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6"},
|
||||
{file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56"},
|
||||
{file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234"},
|
||||
{file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133"},
|
||||
{file = "coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c"},
|
||||
{file = "coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6"},
|
||||
{file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"},
|
||||
{file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"},
|
||||
{file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"},
|
||||
{file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"},
|
||||
{file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca"},
|
||||
{file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"},
|
||||
{file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"},
|
||||
{file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"},
|
||||
{file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"},
|
||||
{file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"},
|
||||
{file = "coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106"},
|
||||
{file = "coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9"},
|
||||
{file = "coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c"},
|
||||
{file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a"},
|
||||
{file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060"},
|
||||
{file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862"},
|
||||
{file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388"},
|
||||
{file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155"},
|
||||
{file = "coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a"},
|
||||
{file = "coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129"},
|
||||
{file = "coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e"},
|
||||
{file = "coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962"},
|
||||
{file = "coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb"},
|
||||
{file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704"},
|
||||
{file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b"},
|
||||
{file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f"},
|
||||
{file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223"},
|
||||
{file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3"},
|
||||
{file = "coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f"},
|
||||
{file = "coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657"},
|
||||
{file = "coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0"},
|
||||
{file = "coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a"},
|
||||
{file = "coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b"},
|
||||
{file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3"},
|
||||
{file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de"},
|
||||
{file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6"},
|
||||
{file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569"},
|
||||
{file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989"},
|
||||
{file = "coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7"},
|
||||
{file = "coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8"},
|
||||
{file = "coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255"},
|
||||
{file = "coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8"},
|
||||
{file = "coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2"},
|
||||
{file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a"},
|
||||
{file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc"},
|
||||
{file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004"},
|
||||
{file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb"},
|
||||
{file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36"},
|
||||
{file = "coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c"},
|
||||
{file = "coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca"},
|
||||
{file = "coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df"},
|
||||
{file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"},
|
||||
]
|
||||
|
||||
[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.2.2"
|
||||
description = "Backport of PEP 654 (exception groups)"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
|
||||
{file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
test = ["pytest (>=6)"]
|
||||
|
||||
[[package]]
|
||||
name = "flake8"
|
||||
version = "7.1.1"
|
||||
description = "the modular source code checker: pep8 pyflakes and co"
|
||||
optional = false
|
||||
python-versions = ">=3.8.1"
|
||||
files = [
|
||||
{file = "flake8-7.1.1-py2.py3-none-any.whl", hash = "sha256:597477df7860daa5aa0fdd84bf5208a043ab96b8e96ab708770ae0364dd03213"},
|
||||
{file = "flake8-7.1.1.tar.gz", hash = "sha256:049d058491e228e03e67b390f311bbf88fce2dbaa8fa673e7aea87b7198b8d38"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
mccabe = ">=0.7.0,<0.8.0"
|
||||
pycodestyle = ">=2.12.0,<2.13.0"
|
||||
pyflakes = ">=3.2.0,<3.3.0"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.8"
|
||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "idna-3.8-py3-none-any.whl", hash = "sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac"},
|
||||
{file = "idna-3.8.tar.gz", hash = "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603"},
|
||||
]
|
||||
|
||||
[[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.13.2"
|
||||
description = "A Python utility / library to sort Python imports."
|
||||
optional = false
|
||||
python-versions = ">=3.8.0"
|
||||
files = [
|
||||
{file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"},
|
||||
{file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
colors = ["colorama (>=0.4.6)"]
|
||||
|
||||
[[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 = "1.11.2"
|
||||
description = "Optional static typing for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "mypy-1.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a"},
|
||||
{file = "mypy-1.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef"},
|
||||
{file = "mypy-1.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41ea707d036a5307ac674ea172875f40c9d55c5394f888b168033177fce47383"},
|
||||
{file = "mypy-1.11.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6e658bd2d20565ea86da7d91331b0eed6d2eee22dc031579e6297f3e12c758c8"},
|
||||
{file = "mypy-1.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:478db5f5036817fe45adb7332d927daa62417159d49783041338921dcf646fc7"},
|
||||
{file = "mypy-1.11.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75746e06d5fa1e91bfd5432448d00d34593b52e7e91a187d981d08d1f33d4385"},
|
||||
{file = "mypy-1.11.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a976775ab2256aadc6add633d44f100a2517d2388906ec4f13231fafbb0eccca"},
|
||||
{file = "mypy-1.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd953f221ac1379050a8a646585a29574488974f79d8082cedef62744f0a0104"},
|
||||
{file = "mypy-1.11.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:57555a7715c0a34421013144a33d280e73c08df70f3a18a552938587ce9274f4"},
|
||||
{file = "mypy-1.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:36383a4fcbad95f2657642a07ba22ff797de26277158f1cc7bd234821468b1b6"},
|
||||
{file = "mypy-1.11.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318"},
|
||||
{file = "mypy-1.11.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36"},
|
||||
{file = "mypy-1.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987"},
|
||||
{file = "mypy-1.11.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca"},
|
||||
{file = "mypy-1.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70"},
|
||||
{file = "mypy-1.11.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:37c7fa6121c1cdfcaac97ce3d3b5588e847aa79b580c1e922bb5d5d2902df19b"},
|
||||
{file = "mypy-1.11.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4a8a53bc3ffbd161b5b2a4fff2f0f1e23a33b0168f1c0778ec70e1a3d66deb86"},
|
||||
{file = "mypy-1.11.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ff93107f01968ed834f4256bc1fc4475e2fecf6c661260066a985b52741ddce"},
|
||||
{file = "mypy-1.11.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:edb91dded4df17eae4537668b23f0ff6baf3707683734b6a818d5b9d0c0c31a1"},
|
||||
{file = "mypy-1.11.2-cp38-cp38-win_amd64.whl", hash = "sha256:ee23de8530d99b6db0573c4ef4bd8f39a2a6f9b60655bf7a1357e585a3486f2b"},
|
||||
{file = "mypy-1.11.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:801ca29f43d5acce85f8e999b1e431fb479cb02d0e11deb7d2abb56bdaf24fd6"},
|
||||
{file = "mypy-1.11.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af8d155170fcf87a2afb55b35dc1a0ac21df4431e7d96717621962e4b9192e70"},
|
||||
{file = "mypy-1.11.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7821776e5c4286b6a13138cc935e2e9b6fde05e081bdebf5cdb2bb97c9df81d"},
|
||||
{file = "mypy-1.11.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:539c570477a96a4e6fb718b8d5c3e0c0eba1f485df13f86d2970c91f0673148d"},
|
||||
{file = "mypy-1.11.2-cp39-cp39-win_amd64.whl", hash = "sha256:3f14cd3d386ac4d05c5a39a51b84387403dadbd936e17cb35882134d4f8f0d24"},
|
||||
{file = "mypy-1.11.2-py3-none-any.whl", hash = "sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12"},
|
||||
{file = "mypy-1.11.2.tar.gz", hash = "sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
mypy-extensions = ">=1.0.0"
|
||||
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
|
||||
typing-extensions = ">=4.6.0"
|
||||
|
||||
[package.extras]
|
||||
dmypy = ["psutil (>=4.0)"]
|
||||
install-types = ["pip"]
|
||||
mypyc = ["setuptools (>=50)"]
|
||||
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 = "2.0.1"
|
||||
description = "Fundamental package for array computing in Python"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "numpy-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fbb536eac80e27a2793ffd787895242b7f18ef792563d742c2d673bfcb75134"},
|
||||
{file = "numpy-2.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:69ff563d43c69b1baba77af455dd0a839df8d25e8590e79c90fcbe1499ebde42"},
|
||||
{file = "numpy-2.0.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:1b902ce0e0a5bb7704556a217c4f63a7974f8f43e090aff03fcf262e0b135e02"},
|
||||
{file = "numpy-2.0.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:f1659887361a7151f89e79b276ed8dff3d75877df906328f14d8bb40bb4f5101"},
|
||||
{file = "numpy-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4658c398d65d1b25e1760de3157011a80375da861709abd7cef3bad65d6543f9"},
|
||||
{file = "numpy-2.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4127d4303b9ac9f94ca0441138acead39928938660ca58329fe156f84b9f3015"},
|
||||
{file = "numpy-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e5eeca8067ad04bc8a2a8731183d51d7cbaac66d86085d5f4766ee6bf19c7f87"},
|
||||
{file = "numpy-2.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9adbd9bb520c866e1bfd7e10e1880a1f7749f1f6e5017686a5fbb9b72cf69f82"},
|
||||
{file = "numpy-2.0.1-cp310-cp310-win32.whl", hash = "sha256:7b9853803278db3bdcc6cd5beca37815b133e9e77ff3d4733c247414e78eb8d1"},
|
||||
{file = "numpy-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:81b0893a39bc5b865b8bf89e9ad7807e16717f19868e9d234bdaf9b1f1393868"},
|
||||
{file = "numpy-2.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75b4e316c5902d8163ef9d423b1c3f2f6252226d1aa5cd8a0a03a7d01ffc6268"},
|
||||
{file = "numpy-2.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6e4eeb6eb2fced786e32e6d8df9e755ce5be920d17f7ce00bc38fcde8ccdbf9e"},
|
||||
{file = "numpy-2.0.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a1e01dcaab205fbece13c1410253a9eea1b1c9b61d237b6fa59bcc46e8e89343"},
|
||||
{file = "numpy-2.0.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:a8fc2de81ad835d999113ddf87d1ea2b0f4704cbd947c948d2f5513deafe5a7b"},
|
||||
{file = "numpy-2.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a3d94942c331dd4e0e1147f7a8699a4aa47dffc11bf8a1523c12af8b2e91bbe"},
|
||||
{file = "numpy-2.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15eb4eca47d36ec3f78cde0a3a2ee24cf05ca7396ef808dda2c0ddad7c2bde67"},
|
||||
{file = "numpy-2.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b83e16a5511d1b1f8a88cbabb1a6f6a499f82c062a4251892d9ad5d609863fb7"},
|
||||
{file = "numpy-2.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f87fec1f9bc1efd23f4227becff04bd0e979e23ca50cc92ec88b38489db3b55"},
|
||||
{file = "numpy-2.0.1-cp311-cp311-win32.whl", hash = "sha256:36d3a9405fd7c511804dc56fc32974fa5533bdeb3cd1604d6b8ff1d292b819c4"},
|
||||
{file = "numpy-2.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:08458fbf403bff5e2b45f08eda195d4b0c9b35682311da5a5a0a0925b11b9bd8"},
|
||||
{file = "numpy-2.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6bf4e6f4a2a2e26655717a1983ef6324f2664d7011f6ef7482e8c0b3d51e82ac"},
|
||||
{file = "numpy-2.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6fddc5fe258d3328cd8e3d7d3e02234c5d70e01ebe377a6ab92adb14039cb4"},
|
||||
{file = "numpy-2.0.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:5daab361be6ddeb299a918a7c0864fa8618af66019138263247af405018b04e1"},
|
||||
{file = "numpy-2.0.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:ea2326a4dca88e4a274ba3a4405eb6c6467d3ffbd8c7d38632502eaae3820587"},
|
||||
{file = "numpy-2.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:529af13c5f4b7a932fb0e1911d3a75da204eff023ee5e0e79c1751564221a5c8"},
|
||||
{file = "numpy-2.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6790654cb13eab303d8402354fabd47472b24635700f631f041bd0b65e37298a"},
|
||||
{file = "numpy-2.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cbab9fc9c391700e3e1287666dfd82d8666d10e69a6c4a09ab97574c0b7ee0a7"},
|
||||
{file = "numpy-2.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:99d0d92a5e3613c33a5f01db206a33f8fdf3d71f2912b0de1739894668b7a93b"},
|
||||
{file = "numpy-2.0.1-cp312-cp312-win32.whl", hash = "sha256:173a00b9995f73b79eb0191129f2455f1e34c203f559dd118636858cc452a1bf"},
|
||||
{file = "numpy-2.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:bb2124fdc6e62baae159ebcfa368708867eb56806804d005860b6007388df171"},
|
||||
{file = "numpy-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bfc085b28d62ff4009364e7ca34b80a9a080cbd97c2c0630bb5f7f770dae9414"},
|
||||
{file = "numpy-2.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8fae4ebbf95a179c1156fab0b142b74e4ba4204c87bde8d3d8b6f9c34c5825ef"},
|
||||
{file = "numpy-2.0.1-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:72dc22e9ec8f6eaa206deb1b1355eb2e253899d7347f5e2fae5f0af613741d06"},
|
||||
{file = "numpy-2.0.1-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:ec87f5f8aca726117a1c9b7083e7656a9d0d606eec7299cc067bb83d26f16e0c"},
|
||||
{file = "numpy-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f682ea61a88479d9498bf2091fdcd722b090724b08b31d63e022adc063bad59"},
|
||||
{file = "numpy-2.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8efc84f01c1cd7e34b3fb310183e72fcdf55293ee736d679b6d35b35d80bba26"},
|
||||
{file = "numpy-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3fdabe3e2a52bc4eff8dc7a5044342f8bd9f11ef0934fcd3289a788c0eb10018"},
|
||||
{file = "numpy-2.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:24a0e1befbfa14615b49ba9659d3d8818a0f4d8a1c5822af8696706fbda7310c"},
|
||||
{file = "numpy-2.0.1-cp39-cp39-win32.whl", hash = "sha256:f9cf5ea551aec449206954b075db819f52adc1638d46a6738253a712d553c7b4"},
|
||||
{file = "numpy-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:e9e81fa9017eaa416c056e5d9e71be93d05e2c3c2ab308d23307a8bc4443c368"},
|
||||
{file = "numpy-2.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:61728fba1e464f789b11deb78a57805c70b2ed02343560456190d0501ba37b0f"},
|
||||
{file = "numpy-2.0.1-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:12f5d865d60fb9734e60a60f1d5afa6d962d8d4467c120a1c0cda6eb2964437d"},
|
||||
{file = "numpy-2.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eacf3291e263d5a67d8c1a581a8ebbcfd6447204ef58828caf69a5e3e8c75990"},
|
||||
{file = "numpy-2.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2c3a346ae20cfd80b6cfd3e60dc179963ef2ea58da5ec074fd3d9e7a1e7ba97f"},
|
||||
{file = "numpy-2.0.1.tar.gz", hash = "sha256:485b87235796410c3519a699cfe1faab097e509e90ebb05dcd098db2ae87e7b3"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "24.1"
|
||||
description = "Core utilities for Python packages"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"},
|
||||
{file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pathspec"
|
||||
version = "0.12.1"
|
||||
description = "Utility library for gitignore style pattern matching of file paths."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
|
||||
{file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "4.2.2"
|
||||
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"},
|
||||
{file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"]
|
||||
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"]
|
||||
type = ["mypy (>=1.8)"]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.5.0"
|
||||
description = "plugin and hook calling mechanisms for python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
|
||||
{file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dev = ["pre-commit", "tox"]
|
||||
testing = ["pytest", "pytest-benchmark"]
|
||||
|
||||
[[package]]
|
||||
name = "pycodestyle"
|
||||
version = "2.12.1"
|
||||
description = "Python style guide checker"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pycodestyle-2.12.1-py2.py3-none-any.whl", hash = "sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3"},
|
||||
{file = "pycodestyle-2.12.1.tar.gz", hash = "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pycparser"
|
||||
version = "2.22"
|
||||
description = "C parser in Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"},
|
||||
{file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyflakes"
|
||||
version = "3.2.0"
|
||||
description = "passive checker of Python programs"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"},
|
||||
{file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyrtlsdr"
|
||||
version = "0.3.0"
|
||||
description = "A Python wrapper for librtlsdr (a driver for Realtek RTL2832U based SDR's)"
|
||||
optional = true
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "pyrtlsdr-0.3.0-py2.py3-none-any.whl", hash = "sha256:4967df42eb89e6bd70602337ae355c9b1231eb1d517fd8cc30f7b862fd1392f6"},
|
||||
{file = "pyrtlsdr-0.3.0.tar.gz", hash = "sha256:fb3e583ba073b861e8e0bc5e62f66f07365e9147f5d36491de4ad62f23e45362"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
lib = ["pyrtlsdrlib"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "8.3.2"
|
||||
description = "pytest: simple powerful testing with Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"},
|
||||
{file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
|
||||
iniconfig = "*"
|
||||
packaging = "*"
|
||||
pluggy = ">=1.5,<2"
|
||||
tomli = {version = ">=1", markers = "python_version < \"3.11\""}
|
||||
|
||||
[package.extras]
|
||||
dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-cov"
|
||||
version = "5.0.0"
|
||||
description = "Pytest plugin for measuring coverage."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"},
|
||||
{file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
coverage = {version = ">=5.2.1", extras = ["toml"]}
|
||||
pytest = ">=4.6"
|
||||
|
||||
[package.extras]
|
||||
testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"]
|
||||
|
||||
[[package]]
|
||||
name = "pyzmq"
|
||||
version = "26.2.0"
|
||||
description = "Python bindings for 0MQ"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "pyzmq-26.2.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:ddf33d97d2f52d89f6e6e7ae66ee35a4d9ca6f36eda89c24591b0c40205a3629"},
|
||||
{file = "pyzmq-26.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dacd995031a01d16eec825bf30802fceb2c3791ef24bcce48fa98ce40918c27b"},
|
||||
{file = "pyzmq-26.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89289a5ee32ef6c439086184529ae060c741334b8970a6855ec0b6ad3ff28764"},
|
||||
{file = "pyzmq-26.2.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5506f06d7dc6ecf1efacb4a013b1f05071bb24b76350832c96449f4a2d95091c"},
|
||||
{file = "pyzmq-26.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ea039387c10202ce304af74def5021e9adc6297067f3441d348d2b633e8166a"},
|
||||
{file = "pyzmq-26.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a2224fa4a4c2ee872886ed00a571f5e967c85e078e8e8c2530a2fb01b3309b88"},
|
||||
{file = "pyzmq-26.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:28ad5233e9c3b52d76196c696e362508959741e1a005fb8fa03b51aea156088f"},
|
||||
{file = "pyzmq-26.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:1c17211bc037c7d88e85ed8b7d8f7e52db6dc8eca5590d162717c654550f7282"},
|
||||
{file = "pyzmq-26.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b8f86dd868d41bea9a5f873ee13bf5551c94cf6bc51baebc6f85075971fe6eea"},
|
||||
{file = "pyzmq-26.2.0-cp310-cp310-win32.whl", hash = "sha256:46a446c212e58456b23af260f3d9fb785054f3e3653dbf7279d8f2b5546b21c2"},
|
||||
{file = "pyzmq-26.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:49d34ab71db5a9c292a7644ce74190b1dd5a3475612eefb1f8be1d6961441971"},
|
||||
{file = "pyzmq-26.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:bfa832bfa540e5b5c27dcf5de5d82ebc431b82c453a43d141afb1e5d2de025fa"},
|
||||
{file = "pyzmq-26.2.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:8f7e66c7113c684c2b3f1c83cdd3376103ee0ce4c49ff80a648643e57fb22218"},
|
||||
{file = "pyzmq-26.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3a495b30fc91db2db25120df5847d9833af237546fd59170701acd816ccc01c4"},
|
||||
{file = "pyzmq-26.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77eb0968da535cba0470a5165468b2cac7772cfb569977cff92e240f57e31bef"},
|
||||
{file = "pyzmq-26.2.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ace4f71f1900a548f48407fc9be59c6ba9d9aaf658c2eea6cf2779e72f9f317"},
|
||||
{file = "pyzmq-26.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92a78853d7280bffb93df0a4a6a2498cba10ee793cc8076ef797ef2f74d107cf"},
|
||||
{file = "pyzmq-26.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:689c5d781014956a4a6de61d74ba97b23547e431e9e7d64f27d4922ba96e9d6e"},
|
||||
{file = "pyzmq-26.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0aca98bc423eb7d153214b2df397c6421ba6373d3397b26c057af3c904452e37"},
|
||||
{file = "pyzmq-26.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1f3496d76b89d9429a656293744ceca4d2ac2a10ae59b84c1da9b5165f429ad3"},
|
||||
{file = "pyzmq-26.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5c2b3bfd4b9689919db068ac6c9911f3fcb231c39f7dd30e3138be94896d18e6"},
|
||||
{file = "pyzmq-26.2.0-cp311-cp311-win32.whl", hash = "sha256:eac5174677da084abf378739dbf4ad245661635f1600edd1221f150b165343f4"},
|
||||
{file = "pyzmq-26.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:5a509df7d0a83a4b178d0f937ef14286659225ef4e8812e05580776c70e155d5"},
|
||||
{file = "pyzmq-26.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:c0e6091b157d48cbe37bd67233318dbb53e1e6327d6fc3bb284afd585d141003"},
|
||||
{file = "pyzmq-26.2.0-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:ded0fc7d90fe93ae0b18059930086c51e640cdd3baebdc783a695c77f123dcd9"},
|
||||
{file = "pyzmq-26.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:17bf5a931c7f6618023cdacc7081f3f266aecb68ca692adac015c383a134ca52"},
|
||||
{file = "pyzmq-26.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55cf66647e49d4621a7e20c8d13511ef1fe1efbbccf670811864452487007e08"},
|
||||
{file = "pyzmq-26.2.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4661c88db4a9e0f958c8abc2b97472e23061f0bc737f6f6179d7a27024e1faa5"},
|
||||
{file = "pyzmq-26.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea7f69de383cb47522c9c208aec6dd17697db7875a4674c4af3f8cfdac0bdeae"},
|
||||
{file = "pyzmq-26.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:7f98f6dfa8b8ccaf39163ce872bddacca38f6a67289116c8937a02e30bbe9711"},
|
||||
{file = "pyzmq-26.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e3e0210287329272539eea617830a6a28161fbbd8a3271bf4150ae3e58c5d0e6"},
|
||||
{file = "pyzmq-26.2.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6b274e0762c33c7471f1a7471d1a2085b1a35eba5cdc48d2ae319f28b6fc4de3"},
|
||||
{file = "pyzmq-26.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:29c6a4635eef69d68a00321e12a7d2559fe2dfccfa8efae3ffb8e91cd0b36a8b"},
|
||||
{file = "pyzmq-26.2.0-cp312-cp312-win32.whl", hash = "sha256:989d842dc06dc59feea09e58c74ca3e1678c812a4a8a2a419046d711031f69c7"},
|
||||
{file = "pyzmq-26.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:2a50625acdc7801bc6f74698c5c583a491c61d73c6b7ea4dee3901bb99adb27a"},
|
||||
{file = "pyzmq-26.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:4d29ab8592b6ad12ebbf92ac2ed2bedcfd1cec192d8e559e2e099f648570e19b"},
|
||||
{file = "pyzmq-26.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9dd8cd1aeb00775f527ec60022004d030ddc51d783d056e3e23e74e623e33726"},
|
||||
{file = "pyzmq-26.2.0-cp313-cp313-macosx_10_15_universal2.whl", hash = "sha256:28c812d9757fe8acecc910c9ac9dafd2ce968c00f9e619db09e9f8f54c3a68a3"},
|
||||
{file = "pyzmq-26.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d80b1dd99c1942f74ed608ddb38b181b87476c6a966a88a950c7dee118fdf50"},
|
||||
{file = "pyzmq-26.2.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c997098cc65e3208eca09303630e84d42718620e83b733d0fd69543a9cab9cb"},
|
||||
{file = "pyzmq-26.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ad1bc8d1b7a18497dda9600b12dc193c577beb391beae5cd2349184db40f187"},
|
||||
{file = "pyzmq-26.2.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:bea2acdd8ea4275e1278350ced63da0b166421928276c7c8e3f9729d7402a57b"},
|
||||
{file = "pyzmq-26.2.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:23f4aad749d13698f3f7b64aad34f5fc02d6f20f05999eebc96b89b01262fb18"},
|
||||
{file = "pyzmq-26.2.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:a4f96f0d88accc3dbe4a9025f785ba830f968e21e3e2c6321ccdfc9aef755115"},
|
||||
{file = "pyzmq-26.2.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ced65e5a985398827cc9276b93ef6dfabe0273c23de8c7931339d7e141c2818e"},
|
||||
{file = "pyzmq-26.2.0-cp313-cp313-win32.whl", hash = "sha256:31507f7b47cc1ead1f6e86927f8ebb196a0bab043f6345ce070f412a59bf87b5"},
|
||||
{file = "pyzmq-26.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:70fc7fcf0410d16ebdda9b26cbd8bf8d803d220a7f3522e060a69a9c87bf7bad"},
|
||||
{file = "pyzmq-26.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:c3789bd5768ab5618ebf09cef6ec2b35fed88709b104351748a63045f0ff9797"},
|
||||
{file = "pyzmq-26.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:034da5fc55d9f8da09015d368f519478a52675e558c989bfcb5cf6d4e16a7d2a"},
|
||||
{file = "pyzmq-26.2.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:c92d73464b886931308ccc45b2744e5968cbaade0b1d6aeb40d8ab537765f5bc"},
|
||||
{file = "pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:794a4562dcb374f7dbbfb3f51d28fb40123b5a2abadee7b4091f93054909add5"},
|
||||
{file = "pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aee22939bb6075e7afededabad1a56a905da0b3c4e3e0c45e75810ebe3a52672"},
|
||||
{file = "pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ae90ff9dad33a1cfe947d2c40cb9cb5e600d759ac4f0fd22616ce6540f72797"},
|
||||
{file = "pyzmq-26.2.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:43a47408ac52647dfabbc66a25b05b6a61700b5165807e3fbd40063fcaf46386"},
|
||||
{file = "pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:25bf2374a2a8433633c65ccb9553350d5e17e60c8eb4de4d92cc6bd60f01d306"},
|
||||
{file = "pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_i686.whl", hash = "sha256:007137c9ac9ad5ea21e6ad97d3489af654381324d5d3ba614c323f60dab8fae6"},
|
||||
{file = "pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:470d4a4f6d48fb34e92d768b4e8a5cc3780db0d69107abf1cd7ff734b9766eb0"},
|
||||
{file = "pyzmq-26.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3b55a4229ce5da9497dd0452b914556ae58e96a4381bb6f59f1305dfd7e53fc8"},
|
||||
{file = "pyzmq-26.2.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9cb3a6460cdea8fe8194a76de8895707e61ded10ad0be97188cc8463ffa7e3a8"},
|
||||
{file = "pyzmq-26.2.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8ab5cad923cc95c87bffee098a27856c859bd5d0af31bd346035aa816b081fe1"},
|
||||
{file = "pyzmq-26.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ed69074a610fad1c2fda66180e7b2edd4d31c53f2d1872bc2d1211563904cd9"},
|
||||
{file = "pyzmq-26.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cccba051221b916a4f5e538997c45d7d136a5646442b1231b916d0164067ea27"},
|
||||
{file = "pyzmq-26.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:0eaa83fc4c1e271c24eaf8fb083cbccef8fde77ec8cd45f3c35a9a123e6da097"},
|
||||
{file = "pyzmq-26.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9edda2df81daa129b25a39b86cb57dfdfe16f7ec15b42b19bfac503360d27a93"},
|
||||
{file = "pyzmq-26.2.0-cp37-cp37m-win32.whl", hash = "sha256:ea0eb6af8a17fa272f7b98d7bebfab7836a0d62738e16ba380f440fceca2d951"},
|
||||
{file = "pyzmq-26.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4ff9dc6bc1664bb9eec25cd17506ef6672d506115095411e237d571e92a58231"},
|
||||
{file = "pyzmq-26.2.0-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:2eb7735ee73ca1b0d71e0e67c3739c689067f055c764f73aac4cc8ecf958ee3f"},
|
||||
{file = "pyzmq-26.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a534f43bc738181aa7cbbaf48e3eca62c76453a40a746ab95d4b27b1111a7d2"},
|
||||
{file = "pyzmq-26.2.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:aedd5dd8692635813368e558a05266b995d3d020b23e49581ddd5bbe197a8ab6"},
|
||||
{file = "pyzmq-26.2.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8be4700cd8bb02cc454f630dcdf7cfa99de96788b80c51b60fe2fe1dac480289"},
|
||||
{file = "pyzmq-26.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fcc03fa4997c447dce58264e93b5aa2d57714fbe0f06c07b7785ae131512732"},
|
||||
{file = "pyzmq-26.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:402b190912935d3db15b03e8f7485812db350d271b284ded2b80d2e5704be780"},
|
||||
{file = "pyzmq-26.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8685fa9c25ff00f550c1fec650430c4b71e4e48e8d852f7ddcf2e48308038640"},
|
||||
{file = "pyzmq-26.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:76589c020680778f06b7e0b193f4b6dd66d470234a16e1df90329f5e14a171cd"},
|
||||
{file = "pyzmq-26.2.0-cp38-cp38-win32.whl", hash = "sha256:8423c1877d72c041f2c263b1ec6e34360448decfb323fa8b94e85883043ef988"},
|
||||
{file = "pyzmq-26.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:76589f2cd6b77b5bdea4fca5992dc1c23389d68b18ccc26a53680ba2dc80ff2f"},
|
||||
{file = "pyzmq-26.2.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:b1d464cb8d72bfc1a3adc53305a63a8e0cac6bc8c5a07e8ca190ab8d3faa43c2"},
|
||||
{file = "pyzmq-26.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4da04c48873a6abdd71811c5e163bd656ee1b957971db7f35140a2d573f6949c"},
|
||||
{file = "pyzmq-26.2.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d049df610ac811dcffdc147153b414147428567fbbc8be43bb8885f04db39d98"},
|
||||
{file = "pyzmq-26.2.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:05590cdbc6b902101d0e65d6a4780af14dc22914cc6ab995d99b85af45362cc9"},
|
||||
{file = "pyzmq-26.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c811cfcd6a9bf680236c40c6f617187515269ab2912f3d7e8c0174898e2519db"},
|
||||
{file = "pyzmq-26.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6835dd60355593de10350394242b5757fbbd88b25287314316f266e24c61d073"},
|
||||
{file = "pyzmq-26.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc6bee759a6bddea5db78d7dcd609397449cb2d2d6587f48f3ca613b19410cfc"},
|
||||
{file = "pyzmq-26.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c530e1eecd036ecc83c3407f77bb86feb79916d4a33d11394b8234f3bd35b940"},
|
||||
{file = "pyzmq-26.2.0-cp39-cp39-win32.whl", hash = "sha256:367b4f689786fca726ef7a6c5ba606958b145b9340a5e4808132cc65759abd44"},
|
||||
{file = "pyzmq-26.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:e6fa2e3e683f34aea77de8112f6483803c96a44fd726d7358b9888ae5bb394ec"},
|
||||
{file = "pyzmq-26.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:7445be39143a8aa4faec43b076e06944b8f9d0701b669df4af200531b21e40bb"},
|
||||
{file = "pyzmq-26.2.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:706e794564bec25819d21a41c31d4df2d48e1cc4b061e8d345d7fb4dd3e94072"},
|
||||
{file = "pyzmq-26.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b435f2753621cd36e7c1762156815e21c985c72b19135dac43a7f4f31d28dd1"},
|
||||
{file = "pyzmq-26.2.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:160c7e0a5eb178011e72892f99f918c04a131f36056d10d9c1afb223fc952c2d"},
|
||||
{file = "pyzmq-26.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c4a71d5d6e7b28a47a394c0471b7e77a0661e2d651e7ae91e0cab0a587859ca"},
|
||||
{file = "pyzmq-26.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:90412f2db8c02a3864cbfc67db0e3dcdbda336acf1c469526d3e869394fe001c"},
|
||||
{file = "pyzmq-26.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2ea4ad4e6a12e454de05f2949d4beddb52460f3de7c8b9d5c46fbb7d7222e02c"},
|
||||
{file = "pyzmq-26.2.0-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fc4f7a173a5609631bb0c42c23d12c49df3966f89f496a51d3eb0ec81f4519d6"},
|
||||
{file = "pyzmq-26.2.0-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:878206a45202247781472a2d99df12a176fef806ca175799e1c6ad263510d57c"},
|
||||
{file = "pyzmq-26.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17c412bad2eb9468e876f556eb4ee910e62d721d2c7a53c7fa31e643d35352e6"},
|
||||
{file = "pyzmq-26.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:0d987a3ae5a71c6226b203cfd298720e0086c7fe7c74f35fa8edddfbd6597eed"},
|
||||
{file = "pyzmq-26.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:39887ac397ff35b7b775db7201095fc6310a35fdbae85bac4523f7eb3b840e20"},
|
||||
{file = "pyzmq-26.2.0-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fdb5b3e311d4d4b0eb8b3e8b4d1b0a512713ad7e6a68791d0923d1aec433d919"},
|
||||
{file = "pyzmq-26.2.0-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:226af7dcb51fdb0109f0016449b357e182ea0ceb6b47dfb5999d569e5db161d5"},
|
||||
{file = "pyzmq-26.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bed0e799e6120b9c32756203fb9dfe8ca2fb8467fed830c34c877e25638c3fc"},
|
||||
{file = "pyzmq-26.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:29c7947c594e105cb9e6c466bace8532dc1ca02d498684128b339799f5248277"},
|
||||
{file = "pyzmq-26.2.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cdeabcff45d1c219636ee2e54d852262e5c2e085d6cb476d938aee8d921356b3"},
|
||||
{file = "pyzmq-26.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35cffef589bcdc587d06f9149f8d5e9e8859920a071df5a2671de2213bef592a"},
|
||||
{file = "pyzmq-26.2.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18c8dc3b7468d8b4bdf60ce9d7141897da103c7a4690157b32b60acb45e333e6"},
|
||||
{file = "pyzmq-26.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7133d0a1677aec369d67dd78520d3fa96dd7f3dcec99d66c1762870e5ea1a50a"},
|
||||
{file = "pyzmq-26.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6a96179a24b14fa6428cbfc08641c779a53f8fcec43644030328f44034c7f1f4"},
|
||||
{file = "pyzmq-26.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4f78c88905461a9203eac9faac157a2a0dbba84a0fd09fd29315db27be40af9f"},
|
||||
{file = "pyzmq-26.2.0.tar.gz", hash = "sha256:070672c258581c8e4f640b5159297580a9974b026043bd4ab0470be9ed324f1f"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
cffi = {version = "*", markers = "implementation_name == \"pypy\""}
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.32.3"
|
||||
description = "Python HTTP for Humans."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"},
|
||||
{file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"},
|
||||
]
|
||||
|
||||
[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.12.2"
|
||||
description = "Backported and Experimental Type Hints for Python 3.8+"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
|
||||
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.2.2"
|
||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"},
|
||||
{file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
|
||||
h2 = ["h2 (>=4,<5)"]
|
||||
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.9"
|
||||
content-hash = "f92c8f8bdcbe5a3351cbe1c026b19b913c9b4502e6ecc3a35b3cbef4d48fee7d"
|
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
|
@ -4,17 +4,31 @@ import warnings
|
||||
try:
|
||||
from . import c_common as common
|
||||
from .c_common import *
|
||||
except:
|
||||
from . import common
|
||||
from .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 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)
|
||||
|
||||
|
@ -5,7 +5,8 @@ cdef unsigned char int_to_char(unsigned char i)
|
||||
|
||||
cpdef str hex2bin(str hexstr)
|
||||
cpdef long bin2int(str binstr)
|
||||
cpdef long hex2int(str binstr)
|
||||
cpdef long hex2int(str hexstr)
|
||||
cpdef str bin2hex(str binstr)
|
||||
|
||||
cpdef unsigned char df(str msg)
|
||||
cpdef long crc(str msg, bint encode=*)
|
||||
@ -16,8 +17,12 @@ 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: ...
|
@ -25,7 +25,7 @@ cdef unsigned char int_to_char(unsigned char i):
|
||||
@cython.boundscheck(False)
|
||||
@cython.overflowcheck(False)
|
||||
cpdef str hex2bin(str hexstr):
|
||||
"""Convert a hexdecimal string to binary string, with zero fillings."""
|
||||
"""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)
|
||||
@ -66,9 +66,14 @@ cpdef long hex2int(str hexstr):
|
||||
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 vaule, bits 1 to 5."""
|
||||
"""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])
|
||||
@ -155,17 +160,7 @@ cpdef long floor(double x):
|
||||
return <long> c_floor(x)
|
||||
|
||||
cpdef str icao(str msg):
|
||||
"""Calculate the ICAO address from an Mode-S message.
|
||||
|
||||
Applicable only with DF4, DF5, DF20, DF21 messages.
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
String: ICAO address in 6 bytes hexadecimal string
|
||||
|
||||
"""
|
||||
"""Calculate the ICAO address from an Mode-S message."""
|
||||
cdef unsigned char DF = df(msg)
|
||||
cdef long c0, c1
|
||||
|
||||
@ -212,14 +207,7 @@ cpdef bint is_icao_assigned(str icao):
|
||||
@cython.boundscheck(False)
|
||||
@cython.wraparound(False)
|
||||
cpdef int typecode(str msg):
|
||||
"""Type code of ADS-B message
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
int: type code number
|
||||
"""
|
||||
"""Type code of ADS-B message"""
|
||||
if df(msg) not in (17, 18):
|
||||
return -1
|
||||
# return None
|
||||
@ -240,7 +228,7 @@ cpdef int cprNL(double lat):
|
||||
|
||||
cdef int nz = 15
|
||||
cdef double a = 1 - cos(pi / (2 * nz))
|
||||
cdef double b = cos(pi / 180.0 * fabs(lat)) ** 2
|
||||
cdef double b = cos(pi / 180 * fabs(lat)) ** 2
|
||||
cdef double nl = 2 * pi / (acos(1 - a / b))
|
||||
NL = floor(nl)
|
||||
return NL
|
||||
@ -248,45 +236,41 @@ cpdef int cprNL(double lat):
|
||||
@cython.boundscheck(False)
|
||||
@cython.wraparound(False)
|
||||
cpdef str idcode(str msg):
|
||||
"""Compute identity (squawk code).
|
||||
|
||||
Applicable only for DF5 or DF21 messages, bit 20-32.
|
||||
credit: @fbyrkjeland
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
string: squawk code
|
||||
|
||||
"""
|
||||
"""Compute identity (squawk code)."""
|
||||
if df(msg) not in [5, 21]:
|
||||
raise RuntimeError("Message must be Downlink Format 5 or 21.")
|
||||
|
||||
cdef bytearray _mbin = bytearray(hex2bin(msg).encode())
|
||||
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[19]
|
||||
cdef unsigned char A1 = mbin[20]
|
||||
cdef unsigned char C2 = mbin[21]
|
||||
cdef unsigned char A2 = mbin[22]
|
||||
cdef unsigned char C4 = mbin[23]
|
||||
cdef unsigned char A4 = mbin[24]
|
||||
# _ = mbin[25]
|
||||
cdef unsigned char B1 = mbin[26]
|
||||
cdef unsigned char D1 = mbin[27]
|
||||
cdef unsigned char B2 = mbin[28]
|
||||
cdef unsigned char D2 = mbin[29]
|
||||
cdef unsigned char B4 = mbin[30]
|
||||
cdef unsigned char D4 = mbin[31]
|
||||
|
||||
# byte1 = int(A4 + A2 + A1, 2)
|
||||
# byte2 = int(B4 + B2 + B1, 2)
|
||||
# byte3 = int(C4 + C2 + C1, 2)
|
||||
# byte4 = int(D4 + D2 + D1, 2)
|
||||
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))
|
||||
@ -295,68 +279,68 @@ cpdef str idcode(str msg):
|
||||
|
||||
return _idcode.decode()
|
||||
|
||||
#return str(byte1) + str(byte2) + str(byte3) + str(byte4)
|
||||
|
||||
|
||||
@cython.boundscheck(False)
|
||||
@cython.wraparound(False)
|
||||
cpdef int altcode(str msg):
|
||||
"""Compute the altitude.
|
||||
|
||||
Applicable only for DF4 or DF20 message, bit 20-32.
|
||||
credit: @fbyrkjeland
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
int: altitude in ft
|
||||
|
||||
"""
|
||||
"""Compute the altitude."""
|
||||
if df(msg) not in [0, 4, 16, 20]:
|
||||
raise RuntimeError("Message must be Downlink Format 0, 4, 16, or 20.")
|
||||
|
||||
# Altitude code, bit 20-32
|
||||
cdef bytearray _mbin = bytearray(hex2bin(msg).encode())
|
||||
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 = mbin[25] # M bit: 26
|
||||
cdef char qbit = mbin[27] # Q bit: 28
|
||||
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 mbit == 48: # unit in ft, "0" -> 48
|
||||
if qbit == 49: # 25ft interval, "1" -> 49
|
||||
vbin = _mbin[19:25] + _mbin[26:27] + _mbin[28:32]
|
||||
if bin2int(binstr) == 0:
|
||||
# altitude unknown or invalid
|
||||
alt = -999999
|
||||
|
||||
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[19]
|
||||
graybytes[2] = mbin[20]
|
||||
graybytes[9] = mbin[21]
|
||||
graybytes[3] = mbin[22]
|
||||
graybytes[10] = mbin[23]
|
||||
graybytes[4] = mbin[24]
|
||||
# _ = mbin[25]
|
||||
graybytes[5] = mbin[26]
|
||||
# cdef char D1 = mbin[27] # always zero
|
||||
graybytes[6] = mbin[28]
|
||||
graybytes[0] = mbin[29]
|
||||
graybytes[7] = mbin[30]
|
||||
graybytes[1] = mbin[31]
|
||||
# graybytes = D2 + D4 + A1 + A2 + A4 + B1 + B2 + B4 + C1 + C2 + C4
|
||||
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())
|
||||
|
||||
if mbit == 49: # unit in meter, "1" -> 49
|
||||
vbin = _mbin[19:25] + _mbin[26:31]
|
||||
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)
|
||||
@ -395,15 +379,7 @@ cpdef str data(str msg):
|
||||
|
||||
|
||||
cpdef bint allzeros(str msg):
|
||||
"""Check if the data bits are all zeros.
|
||||
|
||||
Args:
|
||||
msg (String): 28 bytes hexadecimal message string
|
||||
|
||||
Returns:
|
||||
bool: True or False
|
||||
|
||||
"""
|
||||
"""Check if the data bits are all zeros."""
|
||||
d = hex2bin(data(msg))
|
||||
|
||||
if bin2int(d) > 0:
|
||||
|
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,8 +1,8 @@
|
||||
def tell(msg):
|
||||
from pyModeS import common, adsb, commb, bds
|
||||
def tell(msg: str) -> None:
|
||||
from .. import common, adsb, commb, bds
|
||||
|
||||
def _print(label, value, unit=None):
|
||||
print("%20s: " % label, end="")
|
||||
print("%28s: " % label, end="")
|
||||
print("%s " % value, end="")
|
||||
if unit:
|
||||
print(unit)
|
||||
@ -20,9 +20,14 @@ def tell(msg):
|
||||
_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
|
||||
@ -52,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)")
|
||||
@ -71,6 +78,94 @@ 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("Protocol", "Mode-S Comm-B altitude reply")
|
||||
_print("Altitude", common.altcode(msg), "feet")
|
||||
@ -94,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)
|
||||
@ -116,7 +211,7 @@ def tell(msg):
|
||||
_print("True airspeed", commb.tas50(msg), "knots")
|
||||
|
||||
if BDS == "BDS60":
|
||||
_print("Megnatic Heading", commb.hdg60(msg), "degrees")
|
||||
_print("Magnetic Heading", commb.hdg60(msg), "degrees")
|
||||
_print("Indicated airspeed", commb.ias60(msg), "knots")
|
||||
_print("Mach number", commb.mach60(msg))
|
||||
_print("Vertical rate (Baro)", commb.vr60baro(msg), "feet/minute")
|
||||
|
@ -2,47 +2,121 @@
|
||||
|
||||
The ADS-B module also imports functions from the following modules:
|
||||
|
||||
- pyModeS.decoder.bds.bds05: ``airborne_position()``, ``airborne_position_with_ref()``, ``altitude()``
|
||||
- pyModeS.decoder.bds.bds06: ``surface_position()``, ``surface_position_with_ref()``, ``surface_velocity()``
|
||||
- pyModeS.decoder.bds.bds08: ``category()``, ``callsign()``
|
||||
- pyModeS.decoder.bds.bds09: ``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()``
|
||||
|
||||
"""
|
||||
|
||||
import pyModeS as pms
|
||||
from __future__ import annotations
|
||||
|
||||
from pyModeS import common
|
||||
from datetime import datetime
|
||||
|
||||
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 as altitude05,
|
||||
)
|
||||
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):
|
||||
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.
|
||||
|
||||
@ -64,6 +138,9 @@ def position(msg0, msg1, t0, t1, lat_ref=None, lon_ref=None):
|
||||
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 lat_ref is None or lon_ref is None:
|
||||
raise RuntimeError(
|
||||
@ -85,13 +162,13 @@ def position(msg0, msg1, t0, t1, lat_ref=None, lon_ref=None):
|
||||
raise RuntimeError("Incorrect or inconsistent message types")
|
||||
|
||||
|
||||
def position_with_ref(msg, lat_ref, lon_ref):
|
||||
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 with in 180NM (airborne) or 45NM (surface)
|
||||
The reference position shall be within 180NM (airborne) or 45NM (surface)
|
||||
of the true position.
|
||||
|
||||
Args:
|
||||
@ -105,6 +182,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)
|
||||
|
||||
@ -115,7 +195,7 @@ def position_with_ref(msg, lat_ref, lon_ref):
|
||||
raise RuntimeError("incorrect or inconsistent message types")
|
||||
|
||||
|
||||
def altitude(msg):
|
||||
def altitude(msg: str) -> None | float:
|
||||
"""Decode aircraft altitude.
|
||||
|
||||
Args:
|
||||
@ -127,7 +207,7 @@ def altitude(msg):
|
||||
"""
|
||||
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)
|
||||
|
||||
elif tc >= 5 and tc <= 8:
|
||||
@ -139,39 +219,47 @@ def altitude(msg):
|
||||
return altitude05(msg)
|
||||
|
||||
|
||||
def velocity(msg, source=False):
|
||||
"""Calculate the speed, heading, and vertical rate (handles both airborne or surface message).
|
||||
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).
|
||||
|
||||
Args:
|
||||
msg (str): 28 hexdigits string
|
||||
source (boolean): Include direction and vertical rate sources in return. Default to False.
|
||||
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]: Four or six parameters, including:
|
||||
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 'MAGENTIC_NORTH')
|
||||
- [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.
|
||||
For surface messages, vertical rate and its respective sources are set
|
||||
to None.
|
||||
|
||||
"""
|
||||
if 5 <= typecode(msg) <= 8:
|
||||
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 <= tc <= 8:
|
||||
return surface_velocity(msg, source)
|
||||
|
||||
elif typecode(msg) == 19:
|
||||
elif tc == 19:
|
||||
return airborne_velocity(msg, source)
|
||||
|
||||
else:
|
||||
raise RuntimeError(
|
||||
"incorrect or inconsistent 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)
|
||||
|
||||
@ -181,11 +269,14 @@ def speed_heading(msg):
|
||||
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 (str): 28 hexdigits string
|
||||
@ -196,7 +287,7 @@ def oe_flag(msg):
|
||||
return int(msgbin[53])
|
||||
|
||||
|
||||
def version(msg):
|
||||
def version(msg: str) -> int:
|
||||
"""ADS-B Version
|
||||
|
||||
Args:
|
||||
@ -218,13 +309,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 (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)
|
||||
@ -232,7 +325,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), \
|
||||
@ -240,27 +333,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 (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
|
||||
"""
|
||||
@ -273,17 +375,18 @@ 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:
|
||||
@ -291,10 +394,12 @@ def nic_v1(msg, NICs):
|
||||
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), \
|
||||
@ -302,33 +407,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 | None, int | None]:
|
||||
"""Calculate NIC, navigation integrity category, for ADS-B version 2
|
||||
|
||||
Args:
|
||||
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), \
|
||||
@ -336,7 +445,6 @@ def nic_v2(msg, NICa, NICbc):
|
||||
% msg
|
||||
)
|
||||
|
||||
tc = typecode(msg)
|
||||
NIC = uncertainty.TC_NICv2_lookup[tc]
|
||||
|
||||
if 20 <= tc <= 22:
|
||||
@ -347,15 +455,14 @@ def nic_v2(msg, NICa, NICbc):
|
||||
try:
|
||||
if isinstance(NIC, dict):
|
||||
NIC = NIC[NICs]
|
||||
|
||||
Rc = uncertainty.NICv2[NIC][NICs]["Rc"]
|
||||
except KeyError:
|
||||
Rc = uncertainty.NA
|
||||
return None, None
|
||||
|
||||
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:
|
||||
@ -377,7 +484,7 @@ 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:
|
||||
@ -400,7 +507,7 @@ 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:
|
||||
@ -411,7 +518,7 @@ def nic_b(msg):
|
||||
"""
|
||||
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
|
||||
)
|
||||
@ -422,15 +529,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 (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)
|
||||
|
||||
@ -454,18 +564,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 (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)
|
||||
|
||||
@ -483,18 +596,23 @@ 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 (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)
|
||||
|
@ -1,5 +1,98 @@
|
||||
"""
|
||||
Decoding all call replies DF=11
|
||||
Decode all-call reply messages, with downlink format 11
|
||||
"""
|
||||
|
||||
[To be implemented]
|
||||
"""
|
||||
|
||||
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 not 0, "
|
||||
"or the Flight Status is 2, 3, 4 or 5, "
|
||||
"and either airborne or on the ground"
|
||||
)
|
||||
else:
|
||||
text = None
|
||||
|
||||
return ca, text
|
||||
|
@ -18,16 +18,13 @@
|
||||
Common functions for Mode-S decoding
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
import numpy as np
|
||||
|
||||
from pyModeS.extra import aero
|
||||
from pyModeS 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,12 +33,15 @@ 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:
|
||||
@ -51,7 +51,8 @@ def is50or60(msg, spd_ref, trk_ref, alt_ref):
|
||||
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 (str): 28 hexdigits string
|
||||
mrar (bool): Also infer MRAR (BDS 44) and MHR (BDS 45). Defaults to False.
|
||||
mrar (bool): Also infer MRAR (BDS 44) and MHR (BDS 45).
|
||||
Defaults to False.
|
||||
|
||||
Returns:
|
||||
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,6 +135,8 @@ 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" # identification and category
|
||||
|
@ -1,14 +1,20 @@
|
||||
# ------------------------------------------
|
||||
# BDS 0,5
|
||||
# ADS-B TC=9-18
|
||||
# Airborn position
|
||||
# Airborne position
|
||||
# ------------------------------------------
|
||||
|
||||
from pyModeS import common
|
||||
from __future__ import annotations
|
||||
|
||||
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 hexdigits)
|
||||
@ -34,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)
|
||||
@ -59,30 +65,33 @@ 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 (str): even message (28 hexdigits)
|
||||
@ -95,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
|
||||
@ -110,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
|
||||
@ -120,10 +129,10 @@ 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:
|
||||
@ -135,21 +144,19 @@ 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
|
||||
altcode = altbin[0:6] + "0" + altbin[6:]
|
||||
alt = common.altitude(altcode)
|
||||
if alt != -999999:
|
||||
return alt
|
||||
else:
|
||||
alt = None
|
||||
# return None if altitude is invalid
|
||||
return None
|
||||
else:
|
||||
# GNSS altitude, meters -> feet
|
||||
alt = common.bin2int(mb[8:20]) * 3.28084
|
||||
|
||||
return alt
|
||||
return common.bin2int(altbin) * 3.28084 # type: ignore
|
||||
|
@ -1,13 +1,24 @@
|
||||
# ------------------------------------------
|
||||
# BDS 0,6
|
||||
# ADS-B TC=5-8
|
||||
# Surface movment
|
||||
# Surface movement
|
||||
# ------------------------------------------
|
||||
|
||||
from pyModeS import common
|
||||
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.
|
||||
|
||||
@ -27,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)
|
||||
@ -43,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
|
||||
@ -55,38 +66,41 @@ 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 (str): even message (28 hexdigits)
|
||||
@ -99,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
|
||||
@ -114,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
|
||||
@ -124,19 +138,22 @@ 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, source=False):
|
||||
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 (str): 28 hexdigits string
|
||||
source (boolean): Include direction and vertical rate sources in return. Default to False.
|
||||
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]: Four or six parameters, including:
|
||||
int, float, int, string, [string], [string]:
|
||||
- Speed (kt)
|
||||
- Angle (degree), ground track
|
||||
- Vertical rate, always 0
|
||||
@ -145,7 +162,8 @@ def surface_velocity(msg, source=False):
|
||||
- [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:]
|
||||
@ -153,8 +171,7 @@ def surface_velocity(msg, source=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
|
||||
|
||||
@ -164,18 +181,17 @@ def surface_velocity(msg, source=False):
|
||||
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 source:
|
||||
return spd, trk, 0, "GS", "TRUE_NORTH", None
|
||||
return spd, trk, 0, "GS", "TRUE_NORTH", None # type: ignore
|
||||
else:
|
||||
return spd, trk, 0, "GS"
|
||||
|
@ -1,13 +1,13 @@
|
||||
# ------------------------------------------
|
||||
# BDS 0,8
|
||||
# ADS-B TC=1-4
|
||||
# Aircraft identitification and category
|
||||
# Aircraft identification and category
|
||||
# ------------------------------------------
|
||||
|
||||
from pyModeS import common
|
||||
from ... import common
|
||||
|
||||
|
||||
def category(msg):
|
||||
def category(msg: str) -> int:
|
||||
"""Aircraft category number
|
||||
|
||||
Args:
|
||||
@ -17,7 +17,8 @@ def category(msg):
|
||||
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)
|
||||
@ -25,7 +26,7 @@ def category(msg):
|
||||
return common.bin2int(mebin[5:8])
|
||||
|
||||
|
||||
def callsign(msg):
|
||||
def callsign(msg: str) -> str:
|
||||
"""Aircraft callsign
|
||||
|
||||
Args:
|
||||
@ -34,8 +35,9 @@ def callsign(msg):
|
||||
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,35 +1,41 @@
|
||||
# ------------------------------------------
|
||||
# BDS 0,9
|
||||
# ADS-B TC=19
|
||||
# Aircraft Airborn velocity
|
||||
# Aircraft Airborne velocity
|
||||
# ------------------------------------------
|
||||
|
||||
from pyModeS import common
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
|
||||
from ... import common
|
||||
|
||||
def airborne_velocity(msg, source=False):
|
||||
|
||||
def airborne_velocity(
|
||||
msg: str, source: bool = False
|
||||
) -> None | tuple[None | int, None | float, None | int, str]:
|
||||
"""Decode airborne velocity.
|
||||
|
||||
Args:
|
||||
msg (str): 28 hexdigits string
|
||||
source (boolean): Include direction and vertical rate sources in return. Default to False.
|
||||
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]: Four or six parameters, including:
|
||||
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 'MAGENTIC_NORTH')
|
||||
- [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:]
|
||||
|
||||
@ -38,43 +44,54 @@ def airborne_velocity(msg, source=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)
|
||||
|
||||
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"
|
||||
trk_or_hdg = round(trk, 2)
|
||||
dir_type = "TRUE_NORTH"
|
||||
|
||||
else:
|
||||
if mb[13] == "0":
|
||||
hdg = None
|
||||
else:
|
||||
hdg = common.bin2int(mb[14:24]) / 1024.0 * 360.0
|
||||
hdg = round(hdg, 2)
|
||||
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":
|
||||
@ -82,7 +99,7 @@ def airborne_velocity(msg, source=False):
|
||||
else:
|
||||
spd_type = "TAS"
|
||||
|
||||
dir_type = "MAGENTIC_NORTH"
|
||||
dir_type = "MAGNETIC_NORTH"
|
||||
|
||||
vr_source = "GNSS" if mb[35] == "0" else "BARO"
|
||||
vr_sign = -1 if mb[36] == "1" else 1
|
||||
@ -90,12 +107,19 @@ def airborne_velocity(msg, source=False):
|
||||
vs = None if vr == 0 else int(vr_sign * (vr - 1) * 64)
|
||||
|
||||
if source:
|
||||
return spd, trk_or_hdg, vs, spd_type, dir_type, vr_source
|
||||
return ( # type: ignore
|
||||
spd,
|
||||
trk_or_hdg,
|
||||
vs,
|
||||
spd_type,
|
||||
dir_type,
|
||||
vr_source,
|
||||
)
|
||||
else:
|
||||
return spd, trk_or_hdg, vs, spd_type
|
||||
|
||||
|
||||
def altitude_diff(msg):
|
||||
def altitude_diff(msg: str) -> None | float:
|
||||
"""Decode the differece between GNSS and barometric altitude.
|
||||
|
||||
Args:
|
||||
@ -108,8 +132,10 @@ def altitude_diff(msg):
|
||||
"""
|
||||
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
|
||||
|
@ -3,10 +3,11 @@
|
||||
# Data link capability report
|
||||
# ------------------------------------------
|
||||
|
||||
from pyModeS import common
|
||||
|
||||
from ... import common
|
||||
|
||||
|
||||
def is10(msg):
|
||||
def is10(msg: str) -> bool:
|
||||
"""Check if a message is likely to be BDS code 1,0
|
||||
|
||||
Args:
|
||||
@ -38,7 +39,7 @@ def is10(msg):
|
||||
return True
|
||||
|
||||
|
||||
def ovc10(msg):
|
||||
def ovc10(msg: str) -> int:
|
||||
"""Return the overlay control capability
|
||||
|
||||
Args:
|
||||
|
@ -3,10 +3,12 @@
|
||||
# Common usage GICB capability report
|
||||
# ------------------------------------------
|
||||
|
||||
from pyModeS import common
|
||||
from typing import List
|
||||
|
||||
from ... import common
|
||||
|
||||
|
||||
def is17(msg):
|
||||
def is17(msg: str) -> bool:
|
||||
"""Check if a message is likely to be BDS code 1,7
|
||||
|
||||
Args:
|
||||
@ -21,7 +23,7 @@ def is17(msg):
|
||||
|
||||
d = common.hex2bin(common.data(msg))
|
||||
|
||||
if common.bin2int(d[28:56]) != 0:
|
||||
if common.bin2int(d[24:56]) != 0:
|
||||
return False
|
||||
|
||||
caps = cap17(msg)
|
||||
@ -38,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 (str): 28 hexdigits string
|
||||
|
||||
Returns:
|
||||
list: list of support BDS codes
|
||||
list: list of supported BDS codes
|
||||
"""
|
||||
allbds = [
|
||||
"05",
|
||||
@ -72,14 +74,10 @@ def cap17(msg):
|
||||
"56",
|
||||
"5F",
|
||||
"60",
|
||||
"NA",
|
||||
"NA",
|
||||
"E1",
|
||||
"E2",
|
||||
]
|
||||
|
||||
d = common.hex2bin(common.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"]
|
||||
idx = [i for i, v in enumerate(d[:24]) if v == "1"]
|
||||
capacity = ["BDS" + allbds[i] for i in idx]
|
||||
|
||||
return capacity
|
||||
|
@ -3,10 +3,10 @@
|
||||
# Aircraft identification
|
||||
# ------------------------------------------
|
||||
|
||||
from pyModeS import common
|
||||
from ... import common
|
||||
|
||||
|
||||
def is20(msg):
|
||||
def is20(msg: str) -> bool:
|
||||
"""Check if a message is likely to be BDS code 2,0
|
||||
|
||||
Args:
|
||||
@ -24,15 +24,17 @@ def is20(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:
|
||||
|
@ -3,11 +3,11 @@
|
||||
# ACAS active resolution advisory
|
||||
# ------------------------------------------
|
||||
|
||||
from pyModeS import common
|
||||
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 (str): 28 hexdigits string
|
||||
|
@ -4,10 +4,12 @@
|
||||
# ------------------------------------------
|
||||
|
||||
import warnings
|
||||
from pyModeS import common
|
||||
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:
|
||||
@ -50,7 +52,7 @@ def is40(msg):
|
||||
return True
|
||||
|
||||
|
||||
def selalt40mcp(msg):
|
||||
def selalt40mcp(msg: str) -> Optional[int]:
|
||||
"""Selected altitude, MCP/FCU
|
||||
|
||||
Args:
|
||||
@ -68,7 +70,7 @@ def selalt40mcp(msg):
|
||||
return alt
|
||||
|
||||
|
||||
def selalt40fms(msg):
|
||||
def selalt40fms(msg: str) -> Optional[int]:
|
||||
"""Selected altitude, FMS
|
||||
|
||||
Args:
|
||||
@ -86,7 +88,7 @@ def selalt40fms(msg):
|
||||
return alt
|
||||
|
||||
|
||||
def p40baro(msg):
|
||||
def p40baro(msg: str) -> Optional[float]:
|
||||
"""Barometric pressure setting
|
||||
|
||||
Args:
|
||||
@ -104,17 +106,19 @@ def p40baro(msg):
|
||||
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)
|
||||
|
@ -3,10 +3,12 @@
|
||||
# Meteorological routine air report
|
||||
# ------------------------------------------
|
||||
|
||||
from pyModeS import common
|
||||
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
|
||||
@ -51,7 +53,7 @@ def is44(msg):
|
||||
return True
|
||||
|
||||
|
||||
def wind44(msg):
|
||||
def wind44(msg: str) -> Tuple[Optional[int], Optional[float]]:
|
||||
"""Wind speed and direction.
|
||||
|
||||
Args:
|
||||
@ -68,12 +70,12 @@ def wind44(msg):
|
||||
return None, None
|
||||
|
||||
speed = common.bin2int(d[5:14]) # knots
|
||||
direction = common.bin2int(d[14:23]) * 180.0 / 256.0 # degree
|
||||
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:
|
||||
@ -94,15 +96,13 @@ def temp44(msg):
|
||||
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:
|
||||
@ -122,7 +122,7 @@ def p44(msg):
|
||||
return p
|
||||
|
||||
|
||||
def hum44(msg):
|
||||
def hum44(msg: str) -> Optional[float]:
|
||||
"""humidity
|
||||
|
||||
Args:
|
||||
@ -136,13 +136,13 @@ def hum44(msg):
|
||||
if d[49] == "0":
|
||||
return None
|
||||
|
||||
hm = common.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 (str): 28 hexdigits string
|
||||
|
@ -3,10 +3,12 @@
|
||||
# Meteorological hazard report
|
||||
# ------------------------------------------
|
||||
|
||||
from pyModeS import common
|
||||
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
|
||||
@ -60,7 +62,7 @@ def is45(msg):
|
||||
return True
|
||||
|
||||
|
||||
def turb45(msg):
|
||||
def turb45(msg: str) -> Optional[int]:
|
||||
"""Turbulence.
|
||||
|
||||
Args:
|
||||
@ -78,7 +80,7 @@ def turb45(msg):
|
||||
return turb
|
||||
|
||||
|
||||
def ws45(msg):
|
||||
def ws45(msg: str) -> Optional[int]:
|
||||
"""Wind shear.
|
||||
|
||||
Args:
|
||||
@ -96,7 +98,7 @@ def ws45(msg):
|
||||
return ws
|
||||
|
||||
|
||||
def mb45(msg):
|
||||
def mb45(msg: str) -> Optional[int]:
|
||||
"""Microburst.
|
||||
|
||||
Args:
|
||||
@ -114,7 +116,7 @@ def mb45(msg):
|
||||
return mb
|
||||
|
||||
|
||||
def ic45(msg):
|
||||
def ic45(msg: str) -> Optional[int]:
|
||||
"""Icing.
|
||||
|
||||
Args:
|
||||
@ -132,7 +134,7 @@ def ic45(msg):
|
||||
return ic
|
||||
|
||||
|
||||
def wv45(msg):
|
||||
def wv45(msg: str) -> Optional[int]:
|
||||
"""Wake vortex.
|
||||
|
||||
Args:
|
||||
@ -150,7 +152,7 @@ def wv45(msg):
|
||||
return ws
|
||||
|
||||
|
||||
def temp45(msg):
|
||||
def temp45(msg: str) -> Optional[float]:
|
||||
"""Static air temperature.
|
||||
|
||||
Args:
|
||||
@ -169,12 +171,11 @@ def temp45(msg):
|
||||
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:
|
||||
@ -191,7 +192,7 @@ def p45(msg):
|
||||
return p
|
||||
|
||||
|
||||
def rh45(msg):
|
||||
def rh45(msg: str) -> Optional[int]:
|
||||
"""Radio height.
|
||||
|
||||
Args:
|
||||
|
@ -3,10 +3,12 @@
|
||||
# Track and turn report
|
||||
# ------------------------------------------
|
||||
|
||||
from pyModeS import common
|
||||
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)
|
||||
|
||||
@ -40,7 +42,7 @@ def is50(msg):
|
||||
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)
|
||||
@ -57,7 +59,7 @@ def is50(msg):
|
||||
return True
|
||||
|
||||
|
||||
def roll50(msg):
|
||||
def roll50(msg: str) -> Optional[float]:
|
||||
"""Roll angle, BDS 5,0 message
|
||||
|
||||
Args:
|
||||
@ -78,11 +80,11 @@ def roll50(msg):
|
||||
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:
|
||||
@ -102,16 +104,16 @@ def trk50(msg):
|
||||
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:
|
||||
@ -129,7 +131,7 @@ def gs50(msg):
|
||||
return spd
|
||||
|
||||
|
||||
def rtrk50(msg):
|
||||
def rtrk50(msg: str) -> Optional[float]:
|
||||
"""Track angle rate, BDS 5,0 message
|
||||
|
||||
Args:
|
||||
@ -151,11 +153,11 @@ def rtrk50(msg):
|
||||
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:
|
||||
|
@ -3,10 +3,12 @@
|
||||
# Air-referenced state vector
|
||||
# ------------------------------------------
|
||||
|
||||
from pyModeS import common
|
||||
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)
|
||||
|
||||
@ -58,7 +60,7 @@ def is53(msg):
|
||||
return True
|
||||
|
||||
|
||||
def hdg53(msg):
|
||||
def hdg53(msg: str) -> Optional[float]:
|
||||
"""Magnetic heading, BDS 5,3 message
|
||||
|
||||
Args:
|
||||
@ -78,16 +80,16 @@ def hdg53(msg):
|
||||
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:
|
||||
@ -105,7 +107,7 @@ def ias53(msg):
|
||||
return ias
|
||||
|
||||
|
||||
def mach53(msg):
|
||||
def mach53(msg: str) -> Optional[float]:
|
||||
"""MACH number, DBS 5,3 message
|
||||
|
||||
Args:
|
||||
@ -120,10 +122,10 @@ def mach53(msg):
|
||||
return None
|
||||
|
||||
mach = common.bin2int(d[24:33]) * 0.008
|
||||
return round(mach, 3)
|
||||
return mach
|
||||
|
||||
|
||||
def tas53(msg):
|
||||
def tas53(msg: str) -> Optional[float]:
|
||||
"""Aircraft true airspeed, BDS 5,3 message
|
||||
|
||||
Args:
|
||||
@ -138,10 +140,10 @@ def tas53(msg):
|
||||
return None
|
||||
|
||||
tas = common.bin2int(d[34:46]) * 0.5 # kts
|
||||
return round(tas, 1)
|
||||
return tas
|
||||
|
||||
|
||||
def vr53(msg):
|
||||
def vr53(msg: str) -> Optional[int]:
|
||||
"""Vertical rate
|
||||
|
||||
Args:
|
||||
|
@ -3,10 +3,13 @@
|
||||
# Heading and speed report
|
||||
# ------------------------------------------
|
||||
|
||||
from pyModeS import common
|
||||
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:
|
||||
@ -54,10 +57,18 @@ 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:
|
||||
@ -77,16 +88,16 @@ def hdg60(msg):
|
||||
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:
|
||||
@ -104,7 +115,7 @@ def ias60(msg):
|
||||
return ias
|
||||
|
||||
|
||||
def mach60(msg):
|
||||
def mach60(msg: str) -> Optional[float]:
|
||||
"""Aircraft MACH number
|
||||
|
||||
Args:
|
||||
@ -119,10 +130,10 @@ def mach60(msg):
|
||||
return None
|
||||
|
||||
mach = common.bin2int(d[24:34]) * 2.048 / 512.0
|
||||
return round(mach, 3)
|
||||
return mach
|
||||
|
||||
|
||||
def vr60baro(msg):
|
||||
def vr60baro(msg: str) -> Optional[int]:
|
||||
"""Vertical rate from barometric measurement, this value may be very noisy.
|
||||
|
||||
Args:
|
||||
@ -148,7 +159,7 @@ def vr60baro(msg):
|
||||
return roc
|
||||
|
||||
|
||||
def vr60ins(msg):
|
||||
def vr60ins(msg: str) -> Optional[int]:
|
||||
"""Vertical rate measurd by onbard equiments (IRS, AHRS)
|
||||
|
||||
Args:
|
||||
|
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])
|
@ -23,16 +23,66 @@ MRAR and MHR
|
||||
"""
|
||||
|
||||
# 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",
|
||||
]
|
||||
|
@ -11,25 +11,56 @@ The EHS wrapper imports all functions from the following modules:
|
||||
|
||||
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,14 +10,26 @@ The ELS wrapper imports all functions from the following modules:
|
||||
|
||||
"""
|
||||
|
||||
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",
|
||||
]
|
||||
|
26
pyModeS/decoder/flarm/__init__.py
Normal file
26
pyModeS/decoder/flarm/__init__.py
Normal file
@ -0,0 +1,26 @@
|
||||
from typing import TypedDict
|
||||
from typing_extensions import Annotated
|
||||
|
||||
from .decode import flarm as flarm_decode
|
||||
|
||||
__all__ = ["DecodedMessage", "flarm"]
|
||||
|
||||
|
||||
class DecodedMessage(TypedDict):
|
||||
timestamp: int
|
||||
icao24: str
|
||||
latitude: float
|
||||
longitude: float
|
||||
altitude: Annotated[int, "m"]
|
||||
vertical_speed: Annotated[float, "m/s"]
|
||||
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,5 +1,138 @@
|
||||
"""
|
||||
Warpper for short roll call surveillance replies DF=4/5
|
||||
Decode short roll call surveillance replies, with downlink format 4 or 5
|
||||
"""
|
||||
|
||||
[To be implemented]
|
||||
"""
|
||||
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}},
|
||||
|
@ -1,8 +1,10 @@
|
||||
from pyModeS import common
|
||||
from typing import Optional
|
||||
from .. import common
|
||||
from textwrap import wrap
|
||||
|
||||
|
||||
def uplink_icao(msg):
|
||||
"""Calculate the ICAO address from a Mode-S interrogation (uplink message)"""
|
||||
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)
|
||||
@ -19,7 +21,209 @@ def uplink_icao(msg):
|
||||
return "%06X" % (ad >> 2)
|
||||
|
||||
|
||||
def uf(msg):
|
||||
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 == 1 or 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:
|
||||
# II
|
||||
II = (mbytes[2] >> 4) & 0xF
|
||||
IC = "II" + str(II)
|
||||
elif di == 1:
|
||||
# II
|
||||
II = (mbytes[2] >> 4) & 0xF
|
||||
IC = "II" + str(II)
|
||||
if ((mbytes[3] & 0x40) >> 6) == 1:
|
||||
lockout = True
|
||||
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,
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ 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,8 +1,22 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
import traceback
|
||||
import numpy as np
|
||||
import pyModeS as pms
|
||||
from rtlsdr import RtlSdr
|
||||
import time
|
||||
|
||||
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
|
||||
@ -18,10 +32,10 @@ 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 = [] # amplitude of the sample only
|
||||
self.sdr = RtlSdr()
|
||||
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"
|
||||
@ -31,7 +45,9 @@ class RtlReader(object):
|
||||
self.stop_flag = False
|
||||
self.noise_floor = 1e6
|
||||
|
||||
def _calc_noise(self):
|
||||
self.exception_queue = None
|
||||
|
||||
def _calc_noise(self) -> float:
|
||||
"""Calculate noise floor"""
|
||||
window = smaples_per_microsec * 100
|
||||
total_len = len(self.signal_buffer)
|
||||
@ -42,7 +58,7 @@ class RtlReader(object):
|
||||
)
|
||||
return min(means)
|
||||
|
||||
def _process_buffer(self):
|
||||
def _process_buffer(self) -> list[list[Any]]:
|
||||
"""process raw IQ data in the buffer"""
|
||||
|
||||
# update noise floor
|
||||
@ -62,17 +78,18 @@ class RtlReader(object):
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if self._check_preamble(self.signal_buffer[i : i + pbits * 2]):
|
||||
frame_start = i + pbits * 2
|
||||
frame_end = i + pbits * 2 + (fbits + 1) * 2
|
||||
frame_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]
|
||||
|
||||
threshold = max(frame_pulses) * 0.2
|
||||
|
||||
msgbin = []
|
||||
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
|
||||
|
||||
@ -109,7 +126,7 @@ class RtlReader(object):
|
||||
|
||||
return messages
|
||||
|
||||
def _check_preamble(self, pulses):
|
||||
def _check_preamble(self, pulses) -> bool:
|
||||
if len(pulses) != 16:
|
||||
return False
|
||||
|
||||
@ -119,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:
|
||||
@ -129,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:
|
||||
@ -143,7 +161,7 @@ class RtlReader(object):
|
||||
# print("[*]", msg)
|
||||
pass
|
||||
|
||||
def _read_callback(self, data, rtlsdr_obj):
|
||||
def _read_callback(self, data, rtlsdr_obj) -> None:
|
||||
amp = np.absolute(data)
|
||||
self.signal_buffer.extend(amp.tolist())
|
||||
|
||||
@ -151,17 +169,20 @@ class RtlReader(object):
|
||||
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):
|
||||
def stop(self, *args, **kwargs) -> None:
|
||||
self.sdr.close()
|
||||
|
||||
def run(self, raw_pipe_in=None, stop_flag=None, exception_queue=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
|
||||
|
||||
try:
|
||||
@ -173,8 +194,8 @@ class RtlReader(object):
|
||||
|
||||
except Exception as e:
|
||||
tb = traceback.format_exc()
|
||||
if exception_queue is not None:
|
||||
exception_queue.put(tb)
|
||||
if self.exception_queue is not None:
|
||||
self.exception_queue.put(tb)
|
||||
raise e
|
||||
|
||||
|
||||
|
@ -6,11 +6,7 @@ import time
|
||||
import pyModeS as pms
|
||||
import traceback
|
||||
import zmq
|
||||
|
||||
if sys.version_info > (3, 0):
|
||||
PY_VERSION = 3
|
||||
else:
|
||||
PY_VERSION = 2
|
||||
import math
|
||||
|
||||
|
||||
class TcpClient(object):
|
||||
@ -28,6 +24,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)
|
||||
@ -35,7 +33,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.
|
||||
@ -152,6 +150,103 @@ class TcpClient(object):
|
||||
messages.append([msg, ts])
|
||||
return messages
|
||||
|
||||
def read_beast_buffer_rssi_piaware(self):
|
||||
"""Handle mode-s beast data type.
|
||||
|
||||
<esc> "1" : 6 byte MLAT timestamp, 1 byte signal level,
|
||||
2 byte Mode-AC
|
||||
<esc> "2" : 6 byte MLAT timestamp, 1 byte signal level,
|
||||
7 byte Mode-S short frame
|
||||
<esc> "3" : 6 byte MLAT timestamp, 1 byte signal level,
|
||||
14 byte Mode-S long frame
|
||||
<esc> "4" : 6 byte MLAT timestamp, status data, DIP switch
|
||||
configuration settings (not on Mode-S Beast classic)
|
||||
<esc><esc>: true 0x1a
|
||||
<esc> is 0x1a, and "1", "2" and "3" are 0x31, 0x32 and 0x33
|
||||
|
||||
timestamp:
|
||||
wiki.modesbeast.com/Radarcape:Firmware_Versions#The_GPS_timestamp
|
||||
"""
|
||||
messages_mlat = []
|
||||
msg = []
|
||||
i = 0
|
||||
|
||||
# process the buffer until the last divider <esc> 0x1a
|
||||
# then, reset the self.buffer with the remainder
|
||||
|
||||
while i < len(self.buffer):
|
||||
if self.buffer[i : i + 2] == [0x1A, 0x1A]:
|
||||
msg.append(0x1A)
|
||||
i += 1
|
||||
elif (i == len(self.buffer) - 1) and (self.buffer[i] == 0x1A):
|
||||
# special case where the last bit is 0x1a
|
||||
msg.append(0x1A)
|
||||
elif self.buffer[i] == 0x1A:
|
||||
if i == len(self.buffer) - 1:
|
||||
# special case where the last bit is 0x1a
|
||||
msg.append(0x1A)
|
||||
elif len(msg) > 0:
|
||||
messages_mlat.append(msg)
|
||||
msg = []
|
||||
else:
|
||||
msg.append(self.buffer[i])
|
||||
i += 1
|
||||
|
||||
# save the reminder for next reading cycle, if not empty
|
||||
if len(msg) > 0:
|
||||
reminder = []
|
||||
for i, m in enumerate(msg):
|
||||
if (m == 0x1A) and (i < len(msg) - 1):
|
||||
# rewind 0x1a, except when it is at the last bit
|
||||
reminder.extend([m, m])
|
||||
else:
|
||||
reminder.append(m)
|
||||
self.buffer = [0x1A] + msg
|
||||
else:
|
||||
self.buffer = []
|
||||
|
||||
# extract messages
|
||||
messages = []
|
||||
for mm in messages_mlat:
|
||||
ts = time.time()
|
||||
|
||||
msgtype = mm[0]
|
||||
# print(''.join('%02X' % i for i in mm))
|
||||
|
||||
if msgtype == 0x32:
|
||||
# Mode-S Short Message, 7 byte, 14-len hexstr
|
||||
msg = "".join("%02X" % i for i in mm[8:15])
|
||||
elif msgtype == 0x33:
|
||||
# Mode-S Long Message, 14 byte, 28-len hexstr
|
||||
msg = "".join("%02X" % i for i in mm[8:22])
|
||||
else:
|
||||
# Other message tupe
|
||||
continue
|
||||
|
||||
if len(msg) not in [14, 28]:
|
||||
continue
|
||||
|
||||
'''
|
||||
we get the raw 0-255 byte value (raw_rssi = mm[7])
|
||||
we scale it to 0.0 - 1.0 (voltage = raw_rssi / 255)
|
||||
we convert it to a dBFS power value (rolling the squaring of the voltage into the dB calculation)
|
||||
'''
|
||||
|
||||
df = pms.df(msg)
|
||||
raw_rssi = mm[7] # eighth byte of Mode-S message should contain RSSI value
|
||||
rssi_ratio = raw_rssi / 255
|
||||
signalLevel = rssi_ratio ** 2
|
||||
dbfs_rssi = 10 * math.log10(signalLevel)
|
||||
|
||||
# skip incomplete message
|
||||
if df in [0, 4, 5, 11] and len(msg) != 14:
|
||||
continue
|
||||
if df in [16, 17, 18, 19, 20, 21, 24] and len(msg) != 28:
|
||||
continue
|
||||
|
||||
messages.append([msg, dbfs_rssi, ts])
|
||||
return messages
|
||||
|
||||
def read_skysense_buffer(self):
|
||||
"""Skysense stream format.
|
||||
|
||||
@ -255,6 +350,7 @@ class TcpClient(object):
|
||||
|
||||
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()
|
||||
|
||||
@ -262,9 +358,6 @@ 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))
|
||||
|
||||
@ -282,9 +375,12 @@ class TcpClient(object):
|
||||
|
||||
# raise RuntimeError("test exception")
|
||||
|
||||
except zmq.error.Again:
|
||||
continue
|
||||
except Exception as e:
|
||||
tb = traceback.format_exc()
|
||||
exception_queue.put(tb)
|
||||
if self.exception_queue is not None:
|
||||
self.exception_queue.put(tb)
|
||||
raise e
|
||||
|
||||
|
||||
@ -294,4 +390,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,39 +1,46 @@
|
||||
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 bin2int(binstr):
|
||||
def bin2int(binstr: str) -> int:
|
||||
"""Convert a binary string to integer."""
|
||||
return int(binstr, 2)
|
||||
|
||||
|
||||
def df(msg):
|
||||
def bin2hex(binstr: str) -> str:
|
||||
"""Convert a binary string to hexadecimal string."""
|
||||
return "{0:X}".format(int(binstr, 2))
|
||||
|
||||
|
||||
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 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)
|
||||
|
||||
@ -70,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(
|
||||
@ -98,7 +105,7 @@ def crc_legacy(msg, encode=False):
|
||||
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
|
||||
@ -108,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.
|
||||
@ -120,6 +127,7 @@ def icao(msg):
|
||||
String: ICAO address in 6 bytes hexadecimal string
|
||||
|
||||
"""
|
||||
addr: Optional[str]
|
||||
DF = df(msg)
|
||||
|
||||
if DF in (11, 17, 18):
|
||||
@ -134,7 +142,7 @@ 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
|
||||
@ -163,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:
|
||||
@ -179,7 +187,7 @@ def typecode(msg):
|
||||
return bin2int(tcbin[0:5])
|
||||
|
||||
|
||||
def cprNL(lat):
|
||||
def cprNL(lat: float) -> int:
|
||||
"""NL() function in CPR decoding."""
|
||||
|
||||
if np.isclose(lat, 0):
|
||||
@ -191,17 +199,14 @@ def cprNL(lat):
|
||||
|
||||
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
|
||||
@ -214,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)
|
||||
@ -237,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
|
||||
@ -250,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]:
|
||||
@ -309,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
|
||||
@ -319,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:
|
||||
@ -342,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.
|
||||
@ -357,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,5 +1,6 @@
|
||||
import os
|
||||
import time
|
||||
import traceback
|
||||
import datetime
|
||||
import csv
|
||||
import pyModeS as pms
|
||||
@ -72,8 +73,15 @@ class Decode:
|
||||
"VFOMr": None,
|
||||
"PE_RCu": None,
|
||||
"PE_VPL": None,
|
||||
"hum44" : None,
|
||||
"p44" : None,
|
||||
"temp44" : None,
|
||||
"turb44" : None,
|
||||
"wind44" : None,
|
||||
}
|
||||
|
||||
self.acs[icao]["tc"] = tc
|
||||
self.acs[icao]["icao"] = icao
|
||||
self.acs[icao]["t"] = t
|
||||
self.acs[icao]["live"] = int(t)
|
||||
|
||||
@ -154,7 +162,7 @@ class Decode:
|
||||
ac["nic_bc"] = pms.adsb.nic_b(msg)
|
||||
|
||||
if (5 <= tc <= 8) or (9 <= tc <= 18) or (20 <= tc <= 22):
|
||||
ac["HPL"], ac["RCu"], ac["RCv"] = pms.adsb.nuc_p(msg)
|
||||
ac["NUCp"], ac["HPL"], ac["RCu"], ac["RCv"] = pms.adsb.nuc_p(msg)
|
||||
|
||||
if (ac["ver"] == 1) and ("nic_s" in ac.keys()):
|
||||
ac["Rc"], ac["VPL"] = pms.adsb.nic_v1(msg, ac["nic_s"])
|
||||
@ -163,20 +171,20 @@ class Decode:
|
||||
and ("nic_a" in ac.keys())
|
||||
and ("nic_bc" in ac.keys())
|
||||
):
|
||||
ac["Rc"] = pms.adsb.nic_v2(msg, ac["nic_a"], ac["nic_bc"])
|
||||
ac["NIC"], ac["Rc"] = pms.adsb.nic_v2(msg, ac["nic_a"], ac["nic_bc"])
|
||||
|
||||
if tc == 19:
|
||||
ac["HVE"], ac["VVE"] = pms.adsb.nuc_v(msg)
|
||||
ac["NUCv"], ac["HVE"], ac["VVE"] = pms.adsb.nuc_v(msg)
|
||||
if ac["ver"] in [1, 2]:
|
||||
ac["HFOMr"], ac["VFOMr"] = pms.adsb.nac_v(msg)
|
||||
ac["NACv"], ac["HFOMr"], ac["VFOMr"] = pms.adsb.nac_v(msg)
|
||||
|
||||
if tc == 29:
|
||||
ac["PE_RCu"], ac["PE_VPL"], ac["base"] = pms.adsb.sil(msg, ac["ver"])
|
||||
ac["EPU"], ac["VEPU"] = pms.adsb.nac_p(msg)
|
||||
ac["NACp"], ac["HEPU"], ac["VEPU"] = pms.adsb.nac_p(msg)
|
||||
|
||||
if tc == 31:
|
||||
ac["ver"] = pms.adsb.version(msg)
|
||||
ac["EPU"], ac["VEPU"] = pms.adsb.nac_p(msg)
|
||||
ac["NACp"], ac["HEPU"], ac["VEPU"] = pms.adsb.nac_p(msg)
|
||||
ac["PE_RCu"], ac["PE_VPL"], ac["sil_base"] = pms.adsb.sil(
|
||||
msg, ac["ver"]
|
||||
)
|
||||
@ -193,6 +201,8 @@ class Decode:
|
||||
if icao not in self.acs:
|
||||
continue
|
||||
|
||||
self.acs[icao]["icao"] = icao
|
||||
self.acs[icao]["t"] = t
|
||||
self.acs[icao]["live"] = int(t)
|
||||
|
||||
bds = pms.bds.infer(msg)
|
||||
@ -216,8 +226,10 @@ class Decode:
|
||||
output_buffer.append([t, icao, "rtrk50", rtrk50])
|
||||
|
||||
if trk50:
|
||||
self.acs[icao]["trk50"] = trk50
|
||||
output_buffer.append([t, icao, "trk50", trk50])
|
||||
if gs50:
|
||||
self.acs[icao]["gs50"] = gs50
|
||||
output_buffer.append([t, icao, "gs50", gs50])
|
||||
|
||||
elif bds == "BDS60":
|
||||
@ -231,16 +243,29 @@ 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:
|
||||
self.acs[icao]["roc60baro"] = roc60baro
|
||||
output_buffer.append([t, icao, "roc60baro", roc60baro])
|
||||
if roc60ins:
|
||||
self.acs[icao]["roc60ins"] = roc60ins
|
||||
output_buffer.append([t, icao, "roc60ins", roc60ins])
|
||||
|
||||
elif bds == "BDS44":
|
||||
if(pms.commb.is44(msg)):
|
||||
self.acs[icao]["hum44"] = pms.commb.hum44(msg)
|
||||
self.acs[icao]["p44"] = pms.commb.p44(msg)
|
||||
self.acs[icao]["temp44"] = pms.commb.temp44(msg)
|
||||
self.acs[icao]["turb44"] = pms.commb.turb44(msg)
|
||||
self.acs[icao]["wind44"] = pms.commb.wind44(msg)
|
||||
|
||||
# clear up old data
|
||||
for icao in list(self.acs.keys()):
|
||||
if self.t - self.acs[icao]["live"] > self.cache_timeout:
|
||||
|
@ -1,154 +0,0 @@
|
||||
#!/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
|
||||
|
||||
|
||||
# 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, 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)
|
||||
|
||||
|
||||
raw_pipe_in, raw_pipe_out = multiprocessing.Pipe()
|
||||
ac_pipe_in, ac_pipe_out = multiprocessing.Pipe()
|
||||
exception_queue = multiprocessing.Queue()
|
||||
stop_flag = multiprocessing.Value("b", False)
|
||||
|
||||
if SOURCE == "net":
|
||||
source = NetSource(host=SERVER, port=PORT, rawtype=DATATYPE)
|
||||
elif SOURCE == "rtlsdr":
|
||||
source = RtlSdrSource()
|
||||
|
||||
|
||||
recv_process = multiprocessing.Process(
|
||||
target=source.run, args=(raw_pipe_in, stop_flag, exception_queue)
|
||||
)
|
||||
|
||||
|
||||
decode = Decode(latlon=LATLON, dumpto=DUMPTO)
|
||||
decode_process = multiprocessing.Process(
|
||||
target=decode.run, args=(raw_pipe_out, ac_pipe_in, exception_queue)
|
||||
)
|
||||
|
||||
screen = Screen(uncertainty=UNCERTAINTY)
|
||||
screen_process = multiprocessing.Process(
|
||||
target=screen.run, args=(ac_pipe_out, exception_queue)
|
||||
)
|
||||
|
||||
|
||||
def shutdown():
|
||||
stop_flag.value = True
|
||||
curses.endwin()
|
||||
sys.stdout = sys.__stdout__
|
||||
recv_process.terminate()
|
||||
decode_process.terminate()
|
||||
screen_process.terminate()
|
||||
recv_process.join()
|
||||
decode_process.join()
|
||||
screen_process.join()
|
||||
|
||||
|
||||
def closeall(signal, frame):
|
||||
print("KeyboardInterrupt (ID: {}). Cleaning up...".format(signal))
|
||||
shutdown()
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
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)
|
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)
|
60
pyproject.toml
Normal file
60
pyproject.toml
Normal file
@ -0,0 +1,60 @@
|
||||
[tool.poetry]
|
||||
name = "pyModeS"
|
||||
version = "2.18"
|
||||
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" },
|
||||
]
|
||||
build = "build.py"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
modeslive = "pyModeS.streamer.modeslive:main"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = ">=3.9"
|
||||
numpy = ">=1.26"
|
||||
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 = ['py39', 'py310', 'py311', 'py312']
|
||||
include = '\.pyi?$'
|
||||
|
||||
[tool.isort]
|
||||
line_length = 80
|
||||
profile = "black"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0", "Cython>=0.29.32", "setuptools>=69.1.1"]
|
||||
build-backend = "poetry.core.masonry.api"
|
109
setup.py
109
setup.py
@ -1,109 +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 version:
|
||||
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
|
||||
|
||||
# Compile some parts
|
||||
from setuptools.extension import Extension
|
||||
from Cython.Build import cythonize
|
||||
|
||||
extensions = [Extension("pyModeS.c_common", ["pyModeS/c_common.pyx"])]
|
||||
|
||||
|
||||
# To use a consistent encoding
|
||||
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.5",
|
||||
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 :: Libraries",
|
||||
# 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 :: 3",
|
||||
],
|
||||
ext_modules=cythonize(extensions),
|
||||
# What does your project relate to?
|
||||
keywords="Mode-S ADS-B EHS ELS Comm-B",
|
||||
# You can just specify the packages manually here if your project is
|
||||
# simple. Or you can use find_packages().
|
||||
packages=find_packages(exclude=["contrib", "docs", "tests"]),
|
||||
# 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", "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"],
|
||||
)
|
@ -1,11 +1,7 @@
|
||||
import sys
|
||||
import time
|
||||
import csv
|
||||
import time
|
||||
|
||||
if len(sys.argv) > 1 and sys.argv[1] == "cython":
|
||||
from pyModeS.c_decoder import adsb
|
||||
else:
|
||||
from pyModeS.decoder import adsb
|
||||
from pyModeS.decoder import adsb
|
||||
|
||||
print("===== Decode ADS-B sample data=====")
|
||||
|
||||
|
@ -46,7 +46,7 @@ def bds_info(BDS, m):
|
||||
)
|
||||
|
||||
else:
|
||||
info = None
|
||||
info = []
|
||||
|
||||
return info
|
||||
|
||||
@ -87,5 +87,5 @@ def commb_decode_all(df, n=None):
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
commb_decode_all(df=20, n=100)
|
||||
commb_decode_all(df=21, n=100)
|
||||
commb_decode_all(df=20, n=500)
|
||||
commb_decode_all(df=21, n=500)
|
||||
|
@ -1,4 +1,5 @@
|
||||
from pyModeS import adsb
|
||||
from pytest import approx
|
||||
|
||||
# === TEST ADS-B package ===
|
||||
|
||||
@ -22,7 +23,7 @@ def test_adsb_position():
|
||||
1446332400,
|
||||
1446332405,
|
||||
)
|
||||
assert pos == (49.81755, 6.08442)
|
||||
assert pos == (approx(49.81755, 0.001), approx(6.08442, 0.001))
|
||||
|
||||
|
||||
def test_adsb_position_swap_odd_even():
|
||||
@ -32,26 +33,32 @@ def test_adsb_position_swap_odd_even():
|
||||
1446332405,
|
||||
1446332400,
|
||||
)
|
||||
assert pos == (49.81755, 6.08442)
|
||||
assert pos == (approx(49.81755, 0.001), approx(6.08442, 0.001))
|
||||
|
||||
|
||||
def test_adsb_position_with_ref():
|
||||
pos = adsb.position_with_ref("8D40058B58C901375147EFD09357", 49.0, 6.0)
|
||||
assert pos == (49.82410, 6.06785)
|
||||
assert pos == (approx(49.82410, 0.001), approx(6.06785, 0.001))
|
||||
pos = adsb.position_with_ref("8FC8200A3AB8F5F893096B000000", -43.5, 172.5)
|
||||
assert pos == (-43.48564, 172.53942)
|
||||
assert pos == (approx(-43.48564, 0.001), approx(172.53942, 0.001))
|
||||
|
||||
|
||||
def test_adsb_airborne_position_with_ref():
|
||||
pos = adsb.airborne_position_with_ref("8D40058B58C901375147EFD09357", 49.0, 6.0)
|
||||
assert pos == (49.82410, 6.06785)
|
||||
pos = adsb.airborne_position_with_ref("8D40058B58C904A87F402D3B8C59", 49.0, 6.0)
|
||||
assert pos == (49.81755, 6.08442)
|
||||
pos = adsb.airborne_position_with_ref(
|
||||
"8D40058B58C901375147EFD09357", 49.0, 6.0
|
||||
)
|
||||
assert pos == (approx(49.82410, 0.001), approx(6.06785, 0.001))
|
||||
pos = adsb.airborne_position_with_ref(
|
||||
"8D40058B58C904A87F402D3B8C59", 49.0, 6.0
|
||||
)
|
||||
assert pos == (approx(49.81755, 0.001), approx(6.08442, 0.001))
|
||||
|
||||
|
||||
def test_adsb_surface_position_with_ref():
|
||||
pos = adsb.surface_position_with_ref("8FC8200A3AB8F5F893096B000000", -43.5, 172.5)
|
||||
assert pos == (-43.48564, 172.53942)
|
||||
pos = adsb.surface_position_with_ref(
|
||||
"8FC8200A3AB8F5F893096B000000", -43.5, 172.5
|
||||
)
|
||||
assert pos == (approx(-43.48564, 0.001), approx(172.53942, 0.001))
|
||||
|
||||
|
||||
def test_adsb_surface_position():
|
||||
@ -63,7 +70,7 @@ def test_adsb_surface_position():
|
||||
-43.496,
|
||||
172.558,
|
||||
)
|
||||
assert pos == (-43.48564, 172.53942)
|
||||
assert pos == (approx(-43.48564, 0.001), approx(172.53942, 0.001))
|
||||
|
||||
|
||||
def test_adsb_alt():
|
||||
@ -74,12 +81,33 @@ def test_adsb_velocity():
|
||||
vgs = adsb.velocity("8D485020994409940838175B284F")
|
||||
vas = adsb.velocity("8DA05F219B06B6AF189400CBC33F")
|
||||
vgs_surface = adsb.velocity("8FC8200A3AB8F5F893096B000000")
|
||||
assert vgs == (159, 182.88, -832, "GS")
|
||||
assert vas == (375, 243.98, -2304, "TAS")
|
||||
assert vgs_surface == (19.0, 42.2, 0, "GS")
|
||||
assert vgs == (159, approx(182.88, 0.1), -832, "GS")
|
||||
assert vas == (375, approx(243.98, 0.1), -2304, "TAS")
|
||||
assert vgs_surface == (19, approx(42.2, 0.1), 0, "GS")
|
||||
assert adsb.altitude_diff("8D485020994409940838175B284F") == 550
|
||||
|
||||
|
||||
def test_adsb_emergency():
|
||||
assert not adsb.is_emergency("8DA2C1B6E112B600000000760759")
|
||||
assert adsb.emergency_state("8DA2C1B6E112B600000000760759") == 0
|
||||
assert adsb.emergency_squawk("8DA2C1B6E112B600000000760759") == "6513"
|
||||
|
||||
|
||||
def test_adsb_target_state_status():
|
||||
sel_alt = adsb.selected_altitude("8DA05629EA21485CBF3F8CADAEEB")
|
||||
assert sel_alt == (16992, "MCP/FCU")
|
||||
assert adsb.baro_pressure_setting("8DA05629EA21485CBF3F8CADAEEB") == 1012.8
|
||||
assert adsb.selected_heading("8DA05629EA21485CBF3F8CADAEEB") == approx(
|
||||
66.8, 0.1
|
||||
)
|
||||
assert adsb.autopilot("8DA05629EA21485CBF3F8CADAEEB") is True
|
||||
assert adsb.vnav_mode("8DA05629EA21485CBF3F8CADAEEB") is True
|
||||
assert adsb.altitude_hold_mode("8DA05629EA21485CBF3F8CADAEEB") is False
|
||||
assert adsb.approach_mode("8DA05629EA21485CBF3F8CADAEEB") is False
|
||||
assert adsb.tcas_operational("8DA05629EA21485CBF3F8CADAEEB") is True
|
||||
assert adsb.lnav_mode("8DA05629EA21485CBF3F8CADAEEB") is True
|
||||
|
||||
|
||||
# def test_nic():
|
||||
# assert adsb.nic('8D3C70A390AB11F55B8C57F65FE6') == 0
|
||||
# assert adsb.nic('8DE1C9738A4A430B427D219C8225') == 1
|
||||
|
13
tests/test_allcall.py
Normal file
13
tests/test_allcall.py
Normal file
@ -0,0 +1,13 @@
|
||||
from pyModeS import allcall
|
||||
|
||||
|
||||
def test_icao():
|
||||
assert allcall.icao("5D484FDEA248F5") == "484FDE"
|
||||
|
||||
|
||||
def test_interrogator():
|
||||
assert allcall.interrogator("5D484FDEA248F5") == "SI6"
|
||||
|
||||
|
||||
def test_capability():
|
||||
assert allcall.capability("5D484FDEA248F5")[0] == 5
|
@ -1,6 +1,12 @@
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
from pyModeS import bds
|
||||
|
||||
|
||||
# this one fails on GitHub action for some unknown reason
|
||||
# it looks successful on other Windows instances though
|
||||
# TODO fix later
|
||||
@pytest.mark.skipif(sys.platform == "win32", reason="GitHub Action")
|
||||
def test_bds_infer():
|
||||
assert bds.infer("8D406B902015A678D4D220AA4BDA") == "BDS08"
|
||||
assert bds.infer("8FC8200A3AB8F5F893096B000000") == "BDS06"
|
||||
@ -17,8 +23,8 @@ def test_bds_infer():
|
||||
|
||||
def test_bds_is50or60():
|
||||
assert bds.is50or60("A0001838201584F23468207CDFA5", 0, 0, 0) == None
|
||||
assert bds.is50or60("A0000000FFDA9517000464000000", 182, 237, 1250) == "BDS50"
|
||||
assert bds.is50or60("A0000000919A5927E23444000000", 413, 54, 18700) == "BDS60"
|
||||
assert bds.is50or60("A8001EBCFFFB23286004A73F6A5B", 320, 250, 14000) == "BDS50"
|
||||
assert bds.is50or60("A8001EBCFE1B29287FDCA807BCFC", 320, 250, 14000) == "BDS50"
|
||||
|
||||
|
||||
def test_surface_position():
|
||||
|
@ -1,57 +1,59 @@
|
||||
try:
|
||||
from pyModeS.decoder import c_common as common
|
||||
from pyModeS import c_common
|
||||
|
||||
def test_conversions():
|
||||
assert common.hex2bin("6E406B") == "011011100100000001101011"
|
||||
assert c_common.hex2bin("6E") == "01101110"
|
||||
assert c_common.bin2hex("01101110") == "6E"
|
||||
assert c_common.bin2hex("1101110") == "6E"
|
||||
|
||||
def test_crc_decode():
|
||||
|
||||
assert common.crc("8D406B902015A678D4D220AA4BDA") == 0
|
||||
assert common.crc("8d8960ed58bf053cf11bc5932b7d") == 0
|
||||
assert common.crc("8d45cab390c39509496ca9a32912") == 0
|
||||
assert common.crc("8d74802958c904e6ef4ba0184d5c") == 0
|
||||
assert common.crc("8d4400cd9b0000b4f87000e71a10") == 0
|
||||
assert common.crc("8d4065de58a1054a7ef0218e226a") == 0
|
||||
assert c_common.crc("8D406B902015A678D4D220AA4BDA") == 0
|
||||
assert c_common.crc("8d8960ed58bf053cf11bc5932b7d") == 0
|
||||
assert c_common.crc("8d45cab390c39509496ca9a32912") == 0
|
||||
assert c_common.crc("8d74802958c904e6ef4ba0184d5c") == 0
|
||||
assert c_common.crc("8d4400cd9b0000b4f87000e71a10") == 0
|
||||
assert c_common.crc("8d4065de58a1054a7ef0218e226a") == 0
|
||||
|
||||
assert common.crc("c80b2dca34aa21dd821a04cb64d4") == 10719924
|
||||
assert common.crc("a800089d8094e33a6004e4b8a522") == 4805588
|
||||
assert common.crc("a8000614a50b6d32bed000bbe0ed") == 5659991
|
||||
assert common.crc("a0000410bc900010a40000f5f477") == 11727682
|
||||
assert common.crc("8d4ca251204994b1c36e60a5343d") == 16
|
||||
assert common.crc("b0001718c65632b0a82040715b65") == 353333
|
||||
assert c_common.crc("c80b2dca34aa21dd821a04cb64d4") == 10719924
|
||||
assert c_common.crc("a800089d8094e33a6004e4b8a522") == 4805588
|
||||
assert c_common.crc("a8000614a50b6d32bed000bbe0ed") == 5659991
|
||||
assert c_common.crc("a0000410bc900010a40000f5f477") == 11727682
|
||||
assert c_common.crc("8d4ca251204994b1c36e60a5343d") == 16
|
||||
assert c_common.crc("b0001718c65632b0a82040715b65") == 353333
|
||||
|
||||
def test_crc_encode():
|
||||
parity = common.crc("8D406B902015A678D4D220AA4BDA", encode=True)
|
||||
parity = c_common.crc("8D406B902015A678D4D220AA4BDA", encode=True)
|
||||
assert parity == 11160538
|
||||
|
||||
def test_icao():
|
||||
assert common.icao("8D406B902015A678D4D220AA4BDA") == "406B90"
|
||||
assert common.icao("A0001839CA3800315800007448D9") == "400940"
|
||||
assert common.icao("A000139381951536E024D4CCF6B5") == "3C4DD2"
|
||||
assert common.icao("A000029CFFBAA11E2004727281F1") == "4243D0"
|
||||
assert c_common.icao("8D406B902015A678D4D220AA4BDA") == "406B90"
|
||||
assert c_common.icao("A0001839CA3800315800007448D9") == "400940"
|
||||
assert c_common.icao("A000139381951536E024D4CCF6B5") == "3C4DD2"
|
||||
assert c_common.icao("A000029CFFBAA11E2004727281F1") == "4243D0"
|
||||
|
||||
def test_modes_altcode():
|
||||
assert common.altcode("A02014B400000000000000F9D514") == 32300
|
||||
assert c_common.altcode("A02014B400000000000000F9D514") == 32300
|
||||
|
||||
def test_modes_idcode():
|
||||
assert common.idcode("A800292DFFBBA9383FFCEB903D01") == "1346"
|
||||
assert c_common.idcode("A800292DFFBBA9383FFCEB903D01") == "1346"
|
||||
|
||||
def test_graycode_to_altitude():
|
||||
assert common.gray2alt("00000000010") == -1000
|
||||
assert common.gray2alt("00000001010") == -500
|
||||
assert common.gray2alt("00000011011") == -100
|
||||
assert common.gray2alt("00000011010") == 0
|
||||
assert common.gray2alt("00000011110") == 100
|
||||
assert common.gray2alt("00000010011") == 600
|
||||
assert common.gray2alt("00000110010") == 1000
|
||||
assert common.gray2alt("00001001001") == 5800
|
||||
assert common.gray2alt("00011100100") == 10300
|
||||
assert common.gray2alt("01100011010") == 32000
|
||||
assert common.gray2alt("01110000100") == 46300
|
||||
assert common.gray2alt("01010101100") == 50200
|
||||
assert common.gray2alt("11011110100") == 73200
|
||||
assert common.gray2alt("10000000011") == 126600
|
||||
assert common.gray2alt("10000000001") == 126700
|
||||
assert c_common.gray2alt("00000000010") == -1000
|
||||
assert c_common.gray2alt("00000001010") == -500
|
||||
assert c_common.gray2alt("00000011011") == -100
|
||||
assert c_common.gray2alt("00000011010") == 0
|
||||
assert c_common.gray2alt("00000011110") == 100
|
||||
assert c_common.gray2alt("00000010011") == 600
|
||||
assert c_common.gray2alt("00000110010") == 1000
|
||||
assert c_common.gray2alt("00001001001") == 5800
|
||||
assert c_common.gray2alt("00011100100") == 10300
|
||||
assert c_common.gray2alt("01100011010") == 32000
|
||||
assert c_common.gray2alt("01110000100") == 46300
|
||||
assert c_common.gray2alt("01010101100") == 50200
|
||||
assert c_common.gray2alt("11011110100") == 73200
|
||||
assert c_common.gray2alt("10000000011") == 126600
|
||||
assert c_common.gray2alt("10000000001") == 126700
|
||||
|
||||
|
||||
except:
|
||||
|
@ -1,4 +1,5 @@
|
||||
from pyModeS import bds, commb
|
||||
from pytest import approx
|
||||
|
||||
# from pyModeS import ehs, els # deprecated
|
||||
|
||||
@ -22,30 +23,24 @@ def test_bds40_functions():
|
||||
|
||||
|
||||
def test_bds50_functions():
|
||||
assert bds.bds50.roll50("A000139381951536E024D4CCF6B5") == 2.1
|
||||
assert bds.bds50.roll50("A0001691FFD263377FFCE02B2BF9") == -0.4 # signed value
|
||||
assert bds.bds50.trk50("A000139381951536E024D4CCF6B5") == 114.258
|
||||
assert bds.bds50.gs50("A000139381951536E024D4CCF6B5") == 438
|
||||
assert bds.bds50.rtrk50("A000139381951536E024D4CCF6B5") == 0.125
|
||||
assert bds.bds50.tas50("A000139381951536E024D4CCF6B5") == 424
|
||||
msg1 = "A000139381951536E024D4CCF6B5"
|
||||
msg2 = "A0001691FFD263377FFCE02B2BF9"
|
||||
|
||||
assert commb.roll50("A000139381951536E024D4CCF6B5") == 2.1
|
||||
assert commb.roll50("A0001691FFD263377FFCE02B2BF9") == -0.4 # signed value
|
||||
assert commb.trk50("A000139381951536E024D4CCF6B5") == 114.258
|
||||
assert commb.gs50("A000139381951536E024D4CCF6B5") == 438
|
||||
assert commb.rtrk50("A000139381951536E024D4CCF6B5") == 0.125
|
||||
assert commb.tas50("A000139381951536E024D4CCF6B5") == 424
|
||||
for module in [bds.bds50, commb]:
|
||||
assert module.roll50(msg1) == approx(2.1, 0.01)
|
||||
assert module.roll50(msg2) == approx(-0.35, 0.01) # signed value
|
||||
assert module.trk50(msg1) == approx(114.258, 0.1)
|
||||
assert module.gs50(msg1) == 438
|
||||
assert module.rtrk50(msg1) == 0.125
|
||||
assert module.tas50(msg1) == 424
|
||||
|
||||
|
||||
def test_bds60_functions():
|
||||
assert bds.bds60.hdg60("A00004128F39F91A7E27C46ADC21") == 42.715
|
||||
assert bds.bds60.ias60("A00004128F39F91A7E27C46ADC21") == 252
|
||||
assert bds.bds60.mach60("A00004128F39F91A7E27C46ADC21") == 0.42
|
||||
assert bds.bds60.vr60baro("A00004128F39F91A7E27C46ADC21") == -1920
|
||||
assert bds.bds60.vr60ins("A00004128F39F91A7E27C46ADC21") == -1920
|
||||
msg = "A00004128F39F91A7E27C46ADC21"
|
||||
|
||||
assert commb.hdg60("A00004128F39F91A7E27C46ADC21") == 42.715
|
||||
assert commb.ias60("A00004128F39F91A7E27C46ADC21") == 252
|
||||
assert commb.mach60("A00004128F39F91A7E27C46ADC21") == 0.42
|
||||
assert commb.vr60baro("A00004128F39F91A7E27C46ADC21") == -1920
|
||||
assert commb.vr60ins("A00004128F39F91A7E27C46ADC21") == -1920
|
||||
for module in [bds.bds60, commb]:
|
||||
assert bds.bds60.hdg60(msg) == approx(42.71484)
|
||||
assert bds.bds60.ias60(msg) == 252
|
||||
assert bds.bds60.mach60(msg) == 0.42
|
||||
assert bds.bds60.vr60baro(msg) == -1920
|
||||
assert bds.bds60.vr60ins(msg) == -1920
|
||||
|
@ -1,62 +0,0 @@
|
||||
from pyModeS import common
|
||||
|
||||
|
||||
def test_conversions():
|
||||
assert common.hex2bin("6E406B") == "011011100100000001101011"
|
||||
|
||||
|
||||
def test_crc_decode():
|
||||
assert common.crc_legacy("8D406B902015A678D4D220AA4BDA") == 0
|
||||
|
||||
assert common.crc("8D406B902015A678D4D220AA4BDA") == 0
|
||||
assert common.crc("8d8960ed58bf053cf11bc5932b7d") == 0
|
||||
assert common.crc("8d45cab390c39509496ca9a32912") == 0
|
||||
assert common.crc("8d49d3d4e1089d00000000744c3b") == 0
|
||||
assert common.crc("8d74802958c904e6ef4ba0184d5c") == 0
|
||||
assert common.crc("8d4400cd9b0000b4f87000e71a10") == 0
|
||||
assert common.crc("8d4065de58a1054a7ef0218e226a") == 0
|
||||
|
||||
assert common.crc("c80b2dca34aa21dd821a04cb64d4") == 10719924
|
||||
assert common.crc("a800089d8094e33a6004e4b8a522") == 4805588
|
||||
assert common.crc("a8000614a50b6d32bed000bbe0ed") == 5659991
|
||||
assert common.crc("a0000410bc900010a40000f5f477") == 11727682
|
||||
assert common.crc("8d4ca251204994b1c36e60a5343d") == 16
|
||||
assert common.crc("b0001718c65632b0a82040715b65") == 353333
|
||||
|
||||
|
||||
def test_crc_encode():
|
||||
parity = common.crc("8D406B902015A678D4D220AA4BDA", encode=True)
|
||||
assert parity == 11160538
|
||||
|
||||
|
||||
def test_icao():
|
||||
assert common.icao("8D406B902015A678D4D220AA4BDA") == "406B90"
|
||||
assert common.icao("A0001839CA3800315800007448D9") == "400940"
|
||||
assert common.icao("A000139381951536E024D4CCF6B5") == "3C4DD2"
|
||||
assert common.icao("A000029CFFBAA11E2004727281F1") == "4243D0"
|
||||
|
||||
|
||||
def test_modes_altcode():
|
||||
assert common.altcode("A02014B400000000000000F9D514") == 32300
|
||||
|
||||
|
||||
def test_modes_idcode():
|
||||
assert common.idcode("A800292DFFBBA9383FFCEB903D01") == "1346"
|
||||
|
||||
|
||||
def test_graycode_to_altitude():
|
||||
assert common.gray2alt("00000000010") == -1000
|
||||
assert common.gray2alt("00000001010") == -500
|
||||
assert common.gray2alt("00000011011") == -100
|
||||
assert common.gray2alt("00000011010") == 0
|
||||
assert common.gray2alt("00000011110") == 100
|
||||
assert common.gray2alt("00000010011") == 600
|
||||
assert common.gray2alt("00000110010") == 1000
|
||||
assert common.gray2alt("00001001001") == 5800
|
||||
assert common.gray2alt("00011100100") == 10300
|
||||
assert common.gray2alt("01100011010") == 32000
|
||||
assert common.gray2alt("01110000100") == 46300
|
||||
assert common.gray2alt("01010101100") == 50200
|
||||
assert common.gray2alt("11011110100") == 73200
|
||||
assert common.gray2alt("10000000011") == 126600
|
||||
assert common.gray2alt("10000000001") == 126700
|
64
tests/test_py_common.py
Normal file
64
tests/test_py_common.py
Normal file
@ -0,0 +1,64 @@
|
||||
from pyModeS import py_common
|
||||
|
||||
|
||||
def test_conversions():
|
||||
assert py_common.hex2bin("6E") == "01101110"
|
||||
assert py_common.bin2hex("01101110") == "6E"
|
||||
assert py_common.bin2hex("1101110") == "6E"
|
||||
|
||||
|
||||
def test_crc_decode():
|
||||
assert py_common.crc_legacy("8D406B902015A678D4D220AA4BDA") == 0
|
||||
|
||||
assert py_common.crc("8D406B902015A678D4D220AA4BDA") == 0
|
||||
assert py_common.crc("8d8960ed58bf053cf11bc5932b7d") == 0
|
||||
assert py_common.crc("8d45cab390c39509496ca9a32912") == 0
|
||||
assert py_common.crc("8d49d3d4e1089d00000000744c3b") == 0
|
||||
assert py_common.crc("8d74802958c904e6ef4ba0184d5c") == 0
|
||||
assert py_common.crc("8d4400cd9b0000b4f87000e71a10") == 0
|
||||
assert py_common.crc("8d4065de58a1054a7ef0218e226a") == 0
|
||||
|
||||
assert py_common.crc("c80b2dca34aa21dd821a04cb64d4") == 10719924
|
||||
assert py_common.crc("a800089d8094e33a6004e4b8a522") == 4805588
|
||||
assert py_common.crc("a8000614a50b6d32bed000bbe0ed") == 5659991
|
||||
assert py_common.crc("a0000410bc900010a40000f5f477") == 11727682
|
||||
assert py_common.crc("8d4ca251204994b1c36e60a5343d") == 16
|
||||
assert py_common.crc("b0001718c65632b0a82040715b65") == 353333
|
||||
|
||||
|
||||
def test_crc_encode():
|
||||
parity = py_common.crc("8D406B902015A678D4D220AA4BDA", encode=True)
|
||||
assert parity == 11160538
|
||||
|
||||
|
||||
def test_icao():
|
||||
assert py_common.icao("8D406B902015A678D4D220AA4BDA") == "406B90"
|
||||
assert py_common.icao("A0001839CA3800315800007448D9") == "400940"
|
||||
assert py_common.icao("A000139381951536E024D4CCF6B5") == "3C4DD2"
|
||||
assert py_common.icao("A000029CFFBAA11E2004727281F1") == "4243D0"
|
||||
|
||||
|
||||
def test_modes_altcode():
|
||||
assert py_common.altcode("A02014B400000000000000F9D514") == 32300
|
||||
|
||||
|
||||
def test_modes_idcode():
|
||||
assert py_common.idcode("A800292DFFBBA9383FFCEB903D01") == "1346"
|
||||
|
||||
|
||||
def test_graycode_to_altitude():
|
||||
assert py_common.gray2alt("00000000010") == -1000
|
||||
assert py_common.gray2alt("00000001010") == -500
|
||||
assert py_common.gray2alt("00000011011") == -100
|
||||
assert py_common.gray2alt("00000011010") == 0
|
||||
assert py_common.gray2alt("00000011110") == 100
|
||||
assert py_common.gray2alt("00000010011") == 600
|
||||
assert py_common.gray2alt("00000110010") == 1000
|
||||
assert py_common.gray2alt("00001001001") == 5800
|
||||
assert py_common.gray2alt("00011100100") == 10300
|
||||
assert py_common.gray2alt("01100011010") == 32000
|
||||
assert py_common.gray2alt("01110000100") == 46300
|
||||
assert py_common.gray2alt("01010101100") == 50200
|
||||
assert py_common.gray2alt("11011110100") == 73200
|
||||
assert py_common.gray2alt("10000000011") == 126600
|
||||
assert py_common.gray2alt("10000000001") == 126700
|
22
tests/test_surv.py
Normal file
22
tests/test_surv.py
Normal file
@ -0,0 +1,22 @@
|
||||
from pyModeS import surv
|
||||
|
||||
|
||||
def test_fs():
|
||||
assert surv.fs("2A00516D492B80")[0] == 2
|
||||
|
||||
|
||||
def test_dr():
|
||||
assert surv.dr("2A00516D492B80")[0] == 0
|
||||
|
||||
|
||||
def test_um():
|
||||
assert surv.um("200CBE4ED80137")[0] == 9
|
||||
assert surv.um("200CBE4ED80137")[1] == 1
|
||||
|
||||
|
||||
def test_identity():
|
||||
assert surv.identity("2A00516D492B80") == "0356"
|
||||
|
||||
|
||||
def test_altitude():
|
||||
assert surv.altitude("20001718029FCD") == 36000
|
Loading…
Reference in New Issue
Block a user