Compare commits

..

No commits in common. "master" and "pr_readme" have entirely different histories.

60 changed files with 714 additions and 3405 deletions

View File

@ -1,12 +0,0 @@
[run]
branch = True
include = */pyModeS/*
omit = *tests*
[report]
exclude_lines =
coverage: ignore
raise NotImplementedError
if TYPE_CHECKING:
ignore_errors = True

View File

@ -1,11 +0,0 @@
version: 2
updates:
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "monthly"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"

View File

@ -1,16 +0,0 @@
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 }}

View File

@ -1,70 +0,0 @@
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
View File

@ -5,6 +5,9 @@ __pycache__/
*.py[cod] *.py[cod]
.pytest_cache/ .pytest_cache/
#cython
*.c
# C extensions # C extensions
*.so *.so

View File

@ -1,7 +1,7 @@
The Python ADS-B/Mode-S Decoder The Python ADS-B/Mode-S Decoder
=============================== ===============================
PyModeS is a Python library designed to decode Mode-S (including ADS-B) messages. 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) message. It can be imported to your python project or used as a standalone tool to view and save live traffic data.
This is a project created by Junzi Sun, who works at `TU Delft <https://www.tudelft.nl/en/>`_, `Aerospace Engineering Faculty <https://www.tudelft.nl/en/ae/>`_, `CNS/ATM research group <http://cs.lr.tudelft.nl/atm/>`_. It is supported by many `contributors <https://github.com/junzis/pyModeS/graphs/contributors>`_ from different institutions. 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.
@ -94,13 +94,13 @@ If you want to make use of the (faster) c module, install ``pyModeS`` as follows
# conda (compiled) version # conda (compiled) version
conda install -c conda-forge pymodes conda install -c conda-forge pymodes
# stable version # stable version (to be compiled on your side)
pip install pyModeS pip install pyModeS[fast]
# development version # development version
git clone https://github.com/junzis/pyModeS git clone https://github.com/junzis/pyModeS
cd pyModeS cd pyModeS
poetry install -E rtlsdr pip install .[fast]
View live traffic (modeslive) View live traffic (modeslive)

View File

@ -1,67 +0,0 @@
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
View File

@ -1,881 +0,0 @@
# 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
View File

@ -1,3 +0,0 @@
decoder/flarm/decode.c
extra/demod2400/core.c
c_common.c

View File

@ -4,9 +4,9 @@ import warnings
try: try:
from . import c_common as common from . import c_common as common
from .c_common import * from .c_common import *
except Exception: except:
from . import py_common as common # type: ignore from . import py_common as common
from .py_common import * # type: ignore from .py_common import *
from .decoder import tell from .decoder import tell
from .decoder import adsb from .decoder import adsb
@ -17,18 +17,6 @@ from .decoder import bds
from .extra import aero from .extra import aero
from .extra import tcpclient from .extra import tcpclient
__all__ = [
"common",
"tell",
"adsb",
"commb",
"allcall",
"surv",
"bds",
"aero",
"tcpclient",
]
warnings.simplefilter("once", DeprecationWarning) warnings.simplefilter("once", DeprecationWarning)

View File

@ -1,18 +0,0 @@
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: ...

View File

@ -25,7 +25,7 @@ cdef unsigned char int_to_char(unsigned char i):
@cython.boundscheck(False) @cython.boundscheck(False)
@cython.overflowcheck(False) @cython.overflowcheck(False)
cpdef str hex2bin(str hexstr): cpdef str hex2bin(str hexstr):
"""Convert a hexadecimal string to binary string, with zero fillings.""" """Convert a hexdecimal string to binary string, with zero fillings."""
# num_of_bits = len(hexstr) * 4 # num_of_bits = len(hexstr) * 4
cdef hexbytes = bytes(hexstr.encode()) cdef hexbytes = bytes(hexstr.encode())
cdef Py_ssize_t len_hexstr = PyBytes_GET_SIZE(hexbytes) cdef Py_ssize_t len_hexstr = PyBytes_GET_SIZE(hexbytes)
@ -73,7 +73,7 @@ cpdef str bin2hex(str binstr):
@cython.boundscheck(False) @cython.boundscheck(False)
cpdef unsigned char df(str msg): cpdef unsigned char df(str msg):
"""Decode Downlink Format value, bits 1 to 5.""" """Decode Downlink Format vaule, bits 1 to 5."""
cdef str dfbin = hex2bin(msg[:2]) cdef str dfbin = hex2bin(msg[:2])
# return min(bin2int(dfbin[0:5]), 24) # return min(bin2int(dfbin[0:5]), 24)
cdef long df = bin2int(dfbin[0:5]) cdef long df = bin2int(dfbin[0:5])
@ -228,7 +228,7 @@ cpdef int cprNL(double lat):
cdef int nz = 15 cdef int nz = 15
cdef double a = 1 - cos(pi / (2 * nz)) cdef double a = 1 - cos(pi / (2 * nz))
cdef double b = cos(pi / 180 * fabs(lat)) ** 2 cdef double b = cos(pi / 180.0 * fabs(lat)) ** 2
cdef double nl = 2 * pi / (acos(1 - a / b)) cdef double nl = 2 * pi / (acos(1 - a / b))
NL = floor(nl) NL = floor(nl)
return NL return NL
@ -311,7 +311,7 @@ cpdef int altitude(str binstr):
if bin2int(binstr) == 0: if bin2int(binstr) == 0:
# altitude unknown or invalid # altitude unknown or invalid
alt = -999999 alt = -9999
elif Mbit == 48: # unit in ft, "0" -> 48 elif Mbit == 48: # unit in ft, "0" -> 48
if Qbit == 49: # 25ft interval, "1" -> 49 if Qbit == 49: # 25ft interval, "1" -> 49

View File

@ -1,22 +0,0 @@
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: ...

View File

@ -1,8 +1,8 @@
def tell(msg: str) -> None: def tell(msg: str) -> None:
from .. import common, adsb, commb, bds from pyModeS import common, adsb, commb, bds
def _print(label, value, unit=None): def _print(label, value, unit=None):
print("%28s: " % label, end="") print("%20s: " % label, end="")
print("%s " % value, end="") print("%s " % value, end="")
if unit: if unit:
print(unit) print(unit)
@ -20,14 +20,9 @@ def tell(msg: str) -> None:
_print("Protocol", "Mode-S Extended Squitter (ADS-B)") _print("Protocol", "Mode-S Extended Squitter (ADS-B)")
tc = common.typecode(msg) tc = common.typecode(msg)
if tc is None:
_print("ERROR", "Unknown typecode")
return
if 1 <= tc <= 4: # callsign if 1 <= tc <= 4: # callsign
callsign = adsb.callsign(msg) callsign = adsb.callsign(msg)
_print("Type", "Identification and category") _print("Type", "Identitification and category")
_print("Callsign:", callsign) _print("Callsign:", callsign)
if 5 <= tc <= 8: # surface position if 5 <= tc <= 8: # surface position
@ -57,9 +52,7 @@ def tell(msg: str) -> None:
if tc == 19: if tc == 19:
_print("Type", "Airborne velocity") _print("Type", "Airborne velocity")
velocity = adsb.velocity(msg) spd, trk, vr, t = adsb.velocity(msg)
if velocity is not None:
spd, trk, vr, t = velocity
types = {"GS": "Ground speed", "TAS": "True airspeed"} types = {"GS": "Ground speed", "TAS": "True airspeed"}
_print("Speed", spd, "knots") _print("Speed", spd, "knots")
_print("Track", trk, "degrees") _print("Track", trk, "degrees")
@ -78,94 +71,6 @@ def tell(msg: str) -> None:
_print("CPR Longitude", cprlon) _print("CPR Longitude", cprlon)
_print("Altitude", alt, "feet") _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: if df == 20:
_print("Protocol", "Mode-S Comm-B altitude reply") _print("Protocol", "Mode-S Comm-B altitude reply")
_print("Altitude", common.altcode(msg), "feet") _print("Altitude", common.altcode(msg), "feet")
@ -189,7 +94,7 @@ def tell(msg: str) -> None:
} }
BDS = bds.infer(msg, mrar=True) BDS = bds.infer(msg, mrar=True)
if BDS is not None and BDS in labels.keys(): if BDS in labels.keys():
_print("BDS", "%s (%s)" % (BDS, labels[BDS])) _print("BDS", "%s (%s)" % (BDS, labels[BDS]))
else: else:
_print("BDS", BDS) _print("BDS", BDS)
@ -211,7 +116,7 @@ def tell(msg: str) -> None:
_print("True airspeed", commb.tas50(msg), "knots") _print("True airspeed", commb.tas50(msg), "knots")
if BDS == "BDS60": if BDS == "BDS60":
_print("Magnetic Heading", commb.hdg60(msg), "degrees") _print("Megnatic Heading", commb.hdg60(msg), "degrees")
_print("Indicated airspeed", commb.ias60(msg), "knots") _print("Indicated airspeed", commb.ias60(msg), "knots")
_print("Mach number", commb.mach60(msg)) _print("Mach number", commb.mach60(msg))
_print("Vertical rate (Baro)", commb.vr60baro(msg), "feet/minute") _print("Vertical rate (Baro)", commb.vr60baro(msg), "feet/minute")

View File

@ -2,121 +2,48 @@
The ADS-B module also imports functions from the following modules: The ADS-B module also imports functions from the following modules:
- bds05: ``airborne_position()``, ``airborne_position_with_ref()``, - pyModeS.decoder.bds.bds05: ``airborne_position()``, ``airborne_position_with_ref()``, ``altitude()``
``altitude()`` - pyModeS.decoder.bds.bds06: ``surface_position()``, ``surface_position_with_ref()``, ``surface_velocity()``
- bds06: ``surface_position()``, ``surface_position_with_ref()``, - pyModeS.decoder.bds.bds08: ``category()``, ``callsign()``
``surface_velocity()`` - pyModeS.decoder.bds.bds09: ``airborne_velocity()``, ``altitude_diff()``
- bds08: ``category()``, ``callsign()``
- bds09: ``airborne_velocity()``, ``altitude_diff()``
""" """
from __future__ import annotations import pyModeS as pms
from datetime import datetime from pyModeS import common
from .. import common from pyModeS.decoder import uncertainty
from . import uncertainty
from .bds.bds05 import airborne_position, airborne_position_with_ref # from pyModeS.decoder.bds import bds05, bds06, bds09
from .bds.bds05 import altitude as altitude05 from pyModeS.decoder.bds.bds05 import (
from .bds.bds06 import ( airborne_position,
airborne_position_with_ref,
altitude as altitude05,
)
from pyModeS.decoder.bds.bds06 import (
surface_position, surface_position,
surface_position_with_ref, surface_position_with_ref,
surface_velocity, surface_velocity,
) )
from .bds.bds08 import callsign, category from pyModeS.decoder.bds.bds08 import category, callsign
from .bds.bds09 import airborne_velocity, altitude_diff from pyModeS.decoder.bds.bds09 import airborne_velocity, altitude_diff
from .bds.bds61 import emergency_squawk, emergency_state, is_emergency from pyModeS.decoder.bds.bds61 import is_emergency, emergency_state, emergency_squawk
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: str) -> int: def df(msg):
return common.df(msg) return common.df(msg)
def icao(msg: str) -> None | str: def icao(msg):
return common.icao(msg) return common.icao(msg)
def typecode(msg: str) -> None | int: def typecode(msg):
return common.typecode(msg) return common.typecode(msg)
def position( def position(msg0, msg1, t0, t1, lat_ref=None, lon_ref=None):
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 """Decode surface or airborne position from a pair of even and odd
position messages. position messages.
@ -138,9 +65,6 @@ def position(
tc0 = typecode(msg0) tc0 = typecode(msg0)
tc1 = typecode(msg1) 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 5 <= tc0 <= 8 and 5 <= tc1 <= 8:
if lat_ref is None or lon_ref is None: if lat_ref is None or lon_ref is None:
raise RuntimeError( raise RuntimeError(
@ -162,13 +86,13 @@ def position(
raise RuntimeError("Incorrect or inconsistent message types") raise RuntimeError("Incorrect or inconsistent message types")
def position_with_ref(msg: str, lat_ref: float, lon_ref: float) -> tuple[float, float]: def position_with_ref(msg, lat_ref, lon_ref):
"""Decode position with only one message. """Decode position with only one message.
A reference position is required, which can be previously A reference position is required, which can be previously
calculated location, ground station, or airport location. calculated location, ground station, or airport location.
The function works with both airborne and surface position messages. The function works with both airborne and surface position messages.
The reference position shall be within 180NM (airborne) or 45NM (surface) The reference position shall be with in 180NM (airborne) or 45NM (surface)
of the true position. of the true position.
Args: Args:
@ -182,9 +106,6 @@ def position_with_ref(msg: str, lat_ref: float, lon_ref: float) -> tuple[float,
tc = typecode(msg) tc = typecode(msg)
if tc is None:
raise RuntimeError("incorrect or inconsistent message types")
if 5 <= tc <= 8: if 5 <= tc <= 8:
return surface_position_with_ref(msg, lat_ref, lon_ref) return surface_position_with_ref(msg, lat_ref, lon_ref)
@ -195,7 +116,7 @@ def position_with_ref(msg: str, lat_ref: float, lon_ref: float) -> tuple[float,
raise RuntimeError("incorrect or inconsistent message types") raise RuntimeError("incorrect or inconsistent message types")
def altitude(msg: str) -> None | float: def altitude(msg):
"""Decode aircraft altitude. """Decode aircraft altitude.
Args: Args:
@ -207,7 +128,7 @@ def altitude(msg: str) -> None | float:
""" """
tc = typecode(msg) tc = typecode(msg)
if tc is None or tc < 5 or tc == 19 or tc > 22: if tc < 5 or tc == 19 or tc > 22:
raise RuntimeError("%s: Not a position message" % msg) raise RuntimeError("%s: Not a position message" % msg)
elif tc >= 5 and tc <= 8: elif tc >= 5 and tc <= 8:
@ -219,47 +140,39 @@ def altitude(msg: str) -> None | float:
return altitude05(msg) return altitude05(msg)
def velocity( def velocity(msg, source=False):
msg: str, source: bool = False """Calculate the speed, heading, and vertical rate (handles both airborne or surface message).
) -> None | tuple[None | float, None | float, None | int, str]:
"""Calculate the speed, heading, and vertical rate
(handles both airborne or surface message).
Args: Args:
msg (str): 28 hexdigits string msg (str): 28 hexdigits string
source (boolean): Include direction and vertical rate sources in return. source (boolean): Include direction and vertical rate sources in return. Default to False.
Default to False.
If set to True, the function will return six value instead of four. If set to True, the function will return six value instead of four.
Returns: Returns:
int, float, int, string, [string], [string]: int, float, int, string, [string], [string]: Four or six parameters, including:
- Speed (kt) - Speed (kt)
- Angle (degree), either ground track or heading - Angle (degree), either ground track or heading
- Vertical rate (ft/min) - Vertical rate (ft/min)
- Speed type ('GS' for ground speed, 'AS' for airspeed) - Speed type ('GS' for ground speed, 'AS' for airspeed)
- [Optional] Direction source ('TRUE_NORTH' or 'MAGNETIC_NORTH') - [Optional] Direction source ('TRUE_NORTH' or 'MAGENTIC_NORTH')
- [Optional] Vertical rate source ('BARO' or 'GNSS') - [Optional] Vertical rate source ('BARO' or 'GNSS')
For surface messages, vertical rate and its respective sources are set For surface messages, vertical rate and its respective sources are set to None.
to None.
""" """
tc = typecode(msg) if 5 <= typecode(msg) <= 8:
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) return surface_velocity(msg, source)
elif tc == 19: elif typecode(msg) == 19:
return airborne_velocity(msg, source) return airborne_velocity(msg, source)
else: else:
raise RuntimeError(error) raise RuntimeError(
"incorrect or inconsistent message types, expecting 4<TC<9 or TC=19"
)
def speed_heading(msg: str) -> None | tuple[None | float, None | float]: def speed_heading(msg):
"""Get speed and ground track (or heading) from the velocity message """Get speed and ground track (or heading) from the velocity message
(handles both airborne or surface message) (handles both airborne or surface message)
@ -269,14 +182,11 @@ def speed_heading(msg: str) -> None | tuple[None | float, None | float]:
Returns: Returns:
(int, float): speed (kt), ground track or heading (degree) (int, float): speed (kt), ground track or heading (degree)
""" """
decoded = velocity(msg) spd, trk_or_hdg, rocd, tag = velocity(msg)
if decoded is None:
return None
spd, trk_or_hdg, rocd, tag = decoded
return spd, trk_or_hdg return spd, trk_or_hdg
def oe_flag(msg: str) -> int: def oe_flag(msg):
"""Check the odd/even flag. Bit 54, 0 for even, 1 for odd. """Check the odd/even flag. Bit 54, 0 for even, 1 for odd.
Args: Args:
msg (str): 28 hexdigits string msg (str): 28 hexdigits string
@ -287,7 +197,7 @@ def oe_flag(msg: str) -> int:
return int(msgbin[53]) return int(msgbin[53])
def version(msg: str) -> int: def version(msg):
"""ADS-B Version """ADS-B Version
Args: Args:
@ -309,15 +219,13 @@ def version(msg: str) -> int:
return version return version
def nuc_p(msg: str) -> tuple[int, None | float, None | int, None | int]: def nuc_p(msg):
"""Calculate NUCp, Navigation Uncertainty Category - Position """Calculate NUCp, Navigation Uncertainty Category - Position (ADS-B version 1)
(ADS-B version 1)
Args: Args:
msg (str): 28 hexdigits string, msg (str): 28 hexdigits string,
Returns: Returns:
int: NUCp, Navigation Uncertainty Category (position)
int: Horizontal Protection Limit int: Horizontal Protection Limit
int: 95% Containment Radius - Horizontal (meters) int: 95% Containment Radius - Horizontal (meters)
int: 95% Containment Radius - Vertical (meters) int: 95% Containment Radius - Vertical (meters)
@ -325,7 +233,7 @@ def nuc_p(msg: str) -> tuple[int, None | float, None | int, None | int]:
""" """
tc = typecode(msg) tc = typecode(msg)
if tc is None or tc < 5 or tc is None or tc > 22: if typecode(msg) < 5 or typecode(msg) > 22:
raise RuntimeError( raise RuntimeError(
"%s: Not a surface position message (5<TC<8), \ "%s: Not a surface position message (5<TC<8), \
airborne position message (8<TC<19), \ airborne position message (8<TC<19), \
@ -333,36 +241,27 @@ def nuc_p(msg: str) -> tuple[int, None | float, None | int, None | int]:
% msg % msg
) )
try:
NUCp = uncertainty.TC_NUCp_lookup[tc] NUCp = uncertainty.TC_NUCp_lookup[tc]
index = uncertainty.NUCp.get(NUCp, None) HPL = uncertainty.NUCp[NUCp]["HPL"]
RCu = uncertainty.NUCp[NUCp]["RCu"]
if index is not None: RCv = uncertainty.NUCp[NUCp]["RCv"]
HPL = index["HPL"] except KeyError:
RCu = index["RCu"]
RCv = index["RCv"]
else:
HPL, RCu, RCv = uncertainty.NA, uncertainty.NA, uncertainty.NA HPL, RCu, RCv = uncertainty.NA, uncertainty.NA, uncertainty.NA
if tc in [20, 21]:
RCv = uncertainty.NA RCv = uncertainty.NA
# RCv only available for GNSS height return HPL, RCu, RCv
if tc == 20:
RCv = 4
elif tc == 21:
RCv = 15
return NUCp, HPL, RCu, RCv
def nuc_v(msg: str) -> tuple[int, None | float, None | float]: def nuc_v(msg):
"""Calculate NUCv, Navigation Uncertainty Category - Velocity """Calculate NUCv, Navigation Uncertainty Category - Velocity (ADS-B version 1)
(ADS-B version 1)
Args: Args:
msg (str): 28 hexdigits string, msg (str): 28 hexdigits string,
Returns: Returns:
int: NUCv, Navigation Uncertainty Category (velocity)
int or string: 95% Horizontal Velocity Error int or string: 95% Horizontal Velocity Error
int or string: 95% Vertical Velocity Error int or string: 95% Vertical Velocity Error
""" """
@ -375,18 +274,17 @@ def nuc_v(msg: str) -> tuple[int, None | float, None | float]:
msgbin = common.hex2bin(msg) msgbin = common.hex2bin(msg)
NUCv = common.bin2int(msgbin[42:45]) NUCv = common.bin2int(msgbin[42:45])
index = uncertainty.NUCv.get(NUCv, None)
if index is not None: try:
HVE = index["HVE"] HVE = uncertainty.NUCv[NUCv]["HVE"]
VVE = index["VVE"] VVE = uncertainty.NUCv[NUCv]["VVE"]
else: except KeyError:
HVE, VVE = uncertainty.NA, uncertainty.NA HVE, VVE = uncertainty.NA, uncertainty.NA
return NUCv, HVE, VVE return HVE, VVE
def nic_v1(msg: str, NICs: int) -> tuple[int, None | float, None | float]: def nic_v1(msg, NICs):
"""Calculate NIC, navigation integrity category, for ADS-B version 1 """Calculate NIC, navigation integrity category, for ADS-B version 1
Args: Args:
@ -394,12 +292,10 @@ def nic_v1(msg: str, NICs: int) -> tuple[int, None | float, None | float]:
NICs (int or string): NIC supplement NICs (int or string): NIC supplement
Returns: Returns:
int: NIC, Navigation Integrity Category
int or string: Horizontal Radius of Containment int or string: Horizontal Radius of Containment
int or string: Vertical Protection Limit int or string: Vertical Protection Limit
""" """
tc = typecode(msg) if typecode(msg) < 5 or typecode(msg) > 22:
if tc is None or tc < 5 or tc > 22:
raise RuntimeError( raise RuntimeError(
"%s: Not a surface position message (5<TC<8), \ "%s: Not a surface position message (5<TC<8), \
airborne position message (8<TC<19), \ airborne position message (8<TC<19), \
@ -407,37 +303,33 @@ def nic_v1(msg: str, NICs: int) -> tuple[int, None | float, None | float]:
% msg % msg
) )
tc = typecode(msg)
NIC = uncertainty.TC_NICv1_lookup[tc] NIC = uncertainty.TC_NICv1_lookup[tc]
if isinstance(NIC, dict): if isinstance(NIC, dict):
NIC = NIC[NICs] NIC = NIC[NICs]
d_index = uncertainty.NICv1.get(NIC, None) try:
Rc = uncertainty.NICv1[NIC][NICs]["Rc"]
VPL = uncertainty.NICv1[NIC][NICs]["VPL"]
except KeyError:
Rc, VPL = uncertainty.NA, uncertainty.NA Rc, VPL = uncertainty.NA, uncertainty.NA
if d_index is not None: return Rc, VPL
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: str, NICa: int, NICbc: int) -> tuple[int | None, int | None]: def nic_v2(msg, NICa, NICbc):
"""Calculate NIC, navigation integrity category, for ADS-B version 2 """Calculate NIC, navigation integrity category, for ADS-B version 2
Args: Args:
msg (str): 28 hexdigits string msg (str): 28 hexdigits string
NICa (int or string): NIC supplement - A NICa (int or string): NIC supplement - A
NICbc (int or string): NIC supplement - B or C NICbc (int or srting): NIC supplement - B or C
Returns: Returns:
int: NIC, Navigation Integrity Category
int or string: Horizontal Radius of Containment int or string: Horizontal Radius of Containment
""" """
tc = typecode(msg) if typecode(msg) < 5 or typecode(msg) > 22:
if tc is None or tc < 5 or tc > 22:
raise RuntimeError( raise RuntimeError(
"%s: Not a surface position message (5<TC<8), \ "%s: Not a surface position message (5<TC<8), \
airborne position message (8<TC<19), \ airborne position message (8<TC<19), \
@ -445,6 +337,7 @@ def nic_v2(msg: str, NICa: int, NICbc: int) -> tuple[int | None, int | None]:
% msg % msg
) )
tc = typecode(msg)
NIC = uncertainty.TC_NICv2_lookup[tc] NIC = uncertainty.TC_NICv2_lookup[tc]
if 20 <= tc <= 22: if 20 <= tc <= 22:
@ -455,14 +348,15 @@ def nic_v2(msg: str, NICa: int, NICbc: int) -> tuple[int | None, int | None]:
try: try:
if isinstance(NIC, dict): if isinstance(NIC, dict):
NIC = NIC[NICs] NIC = NIC[NICs]
Rc = uncertainty.NICv2[NIC][NICs]["Rc"] Rc = uncertainty.NICv2[NIC][NICs]["Rc"]
except KeyError: except KeyError:
return None, None Rc = uncertainty.NA
return NIC, Rc # type: ignore return Rc
def nic_s(msg: str) -> int: def nic_s(msg):
"""Obtain NIC supplement bit, TC=31 message """Obtain NIC supplement bit, TC=31 message
Args: Args:
@ -484,7 +378,7 @@ def nic_s(msg: str) -> int:
return nic_s return nic_s
def nic_a_c(msg: str) -> tuple[int, int]: def nic_a_c(msg):
"""Obtain NICa/c, navigation integrity category supplements a and c """Obtain NICa/c, navigation integrity category supplements a and c
Args: Args:
@ -507,7 +401,7 @@ def nic_a_c(msg: str) -> tuple[int, int]:
return nic_a, nic_c return nic_a, nic_c
def nic_b(msg: str) -> int: def nic_b(msg):
"""Obtain NICb, navigation integrity category supplement-b """Obtain NICb, navigation integrity category supplement-b
Args: Args:
@ -518,7 +412,7 @@ def nic_b(msg: str) -> int:
""" """
tc = typecode(msg) tc = typecode(msg)
if tc is None or tc < 9 or tc > 18: if tc < 9 or tc > 18:
raise RuntimeError( raise RuntimeError(
"%s: Not a airborne position message, expecting 8<TC<19" % msg "%s: Not a airborne position message, expecting 8<TC<19" % msg
) )
@ -529,18 +423,15 @@ def nic_b(msg: str) -> int:
return nic_b return nic_b
def nac_p(msg: str) -> tuple[int, int | None, int | None]: def nac_p(msg):
"""Calculate NACp, Navigation Accuracy Category - Position """Calculate NACp, Navigation Accuracy Category - Position
Args: Args:
msg (str): 28 hexdigits string, TC = 29 or 31 msg (str): 28 hexdigits string, TC = 29 or 31
Returns: Returns:
int: NACp, Navigation Accuracy Category (position) int or string: 95% horizontal accuracy bounds, Estimated Position Uncertainty
int or string: 95% horizontal accuracy bounds, int or string: 95% vertical accuracy bounds, Vertical Estimated Position Uncertainty
Estimated Position Uncertainty
int or string: 95% vertical accuracy bounds,
Vertical Estimated Position Uncertainty
""" """
tc = typecode(msg) tc = typecode(msg)
@ -564,21 +455,18 @@ def nac_p(msg: str) -> tuple[int, int | None, int | None]:
except KeyError: except KeyError:
EPU, VEPU = uncertainty.NA, uncertainty.NA EPU, VEPU = uncertainty.NA, uncertainty.NA
return NACp, EPU, VEPU return EPU, VEPU
def nac_v(msg: str) -> tuple[int, float | None, float | None]: def nac_v(msg):
"""Calculate NACv, Navigation Accuracy Category - Velocity """Calculate NACv, Navigation Accuracy Category - Velocity
Args: Args:
msg (str): 28 hexdigits string, TC = 19 msg (str): 28 hexdigits string, TC = 19
Returns: Returns:
int: NACv, Navigation Accuracy Category (velocity) int or string: 95% horizontal accuracy bounds for velocity, Horizontal Figure of Merit
int or string: 95% horizontal accuracy bounds for velocity, int or string: 95% vertical accuracy bounds for velocity, Vertical Figure of Merit
Horizontal Figure of Merit
int or string: 95% vertical accuracy bounds for velocity,
Vertical Figure of Merit
""" """
tc = typecode(msg) tc = typecode(msg)
@ -596,23 +484,18 @@ def nac_v(msg: str) -> tuple[int, float | None, float | None]:
except KeyError: except KeyError:
HFOMr, VFOMr = uncertainty.NA, uncertainty.NA HFOMr, VFOMr = uncertainty.NA, uncertainty.NA
return NACv, HFOMr, VFOMr return HFOMr, VFOMr
def sil( def sil(msg, version):
msg: str,
version: None | int,
) -> tuple[float | None, float | None, str]:
"""Calculate SIL, Surveillance Integrity Level """Calculate SIL, Surveillance Integrity Level
Args: Args:
msg (str): 28 hexdigits string with TC = 29, 31 msg (str): 28 hexdigits string with TC = 29, 31
Returns: Returns:
int or string: int or string: Probability of exceeding Horizontal Radius of Containment RCu
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 Vertical Integrity Containment Region VPL
string: SIL supplement based on per "hour" or "sample", or 'unknown' string: SIL supplement based on per "hour" or "sample", or 'unknown'
""" """
tc = typecode(msg) tc = typecode(msg)

View File

@ -1,22 +1,14 @@
""" """
Decode all-call reply messages, with downlink format 11 Decode all-call reply messages, with dowlink format 11
""" """
from pyModeS import common
from __future__ import annotations
from typing import Callable, TypeVar
from .. import common
T = TypeVar("T")
F = Callable[[str], T]
def _checkdf(func: F[T]) -> F[T]: def _checkdf(func):
"""Ensure downlink format is 11.""" """Ensure downlink format is 11."""
def wrapper(msg: str) -> T: def wrapper(msg):
df = common.df(msg) df = common.df(msg)
if df != 11: if df != 11:
raise RuntimeError( raise RuntimeError(
@ -28,7 +20,7 @@ def _checkdf(func: F[T]) -> F[T]:
@_checkdf @_checkdf
def icao(msg: str) -> None | str: def icao(msg):
"""Decode transponder code (ICAO address). """Decode transponder code (ICAO address).
Args: Args:
@ -41,7 +33,7 @@ def icao(msg: str) -> None | str:
@_checkdf @_checkdf
def interrogator(msg: str) -> str: def interrogator(msg):
"""Decode interrogator identifier code. """Decode interrogator identifier code.
Args: Args:
@ -50,20 +42,19 @@ def interrogator(msg: str) -> str:
int: interrogator identifier code int: interrogator identifier code
""" """
# the CRC remainder contains the CL and IC field. # the CRC remainder contains the CL and IC field. top three bits are CL field and last four bits are IC field.
# the top three bits are CL field and last four bits are IC field.
remainder = common.crc(msg) remainder = common.crc(msg)
if remainder > 79: if remainder > 79:
IC = "corrupt IC" IC = "corrupt IC"
elif remainder < 16: elif remainder < 16:
IC = "II" + str(remainder) IC="II"+str(remainder)
else: else:
IC = "SI" + str(remainder - 16) IC="SI"+str(remainder-16)
return IC return IC
@_checkdf @_checkdf
def capability(msg: str) -> tuple[int, None | str]: def capability(msg):
"""Decode transponder capability. """Decode transponder capability.
Args: Args:
@ -82,16 +73,9 @@ def capability(msg: str) -> tuple[int, None | str]:
elif ca == 5: elif ca == 5:
text = "level 2 transponder, ability to set CA to 7, airborne" text = "level 2 transponder, ability to set CA to 7, airborne"
elif ca == 6: elif ca == 6:
text = ( text = "evel 2 transponder, ability to set CA to 7, either airborne or ground"
"evel 2 transponder, ability to set CA to 7, "
"either airborne or ground"
)
elif ca == 7: elif ca == 7:
text = ( text = "Downlink Request value is 0,or the Flight Status is 2, 3, 4 or 5, either airborne or on the ground"
"Downlink Request value is not 0, "
"or the Flight Status is 2, 3, 4 or 5, "
"and either airborne or on the ground"
)
else: else:
text = None text = None

View File

@ -18,13 +18,16 @@
Common functions for Mode-S decoding Common functions for Mode-S decoding
""" """
from typing import Optional
import numpy as np import numpy as np
from ... import common from pyModeS.extra import aero
from ...extra import aero from pyModeS import common
from . import ( # noqa: F401
from pyModeS.decoder.bds import (
bds05,
bds06,
bds08,
bds09,
bds10, bds10,
bds17, bds17,
bds20, bds20,
@ -33,15 +36,12 @@ from . import ( # noqa: F401
bds44, bds44,
bds45, bds45,
bds50, bds50,
bds53,
bds60, bds60,
bds61,
bds62,
) )
def is50or60( def is50or60(msg, spd_ref, trk_ref, alt_ref):
msg: str, spd_ref: float, trk_ref: float, alt_ref: float
) -> Optional[str]:
"""Use reference ground speed and trk to determine BDS50 and DBS60. """Use reference ground speed and trk to determine BDS50 and DBS60.
Args: Args:
@ -51,8 +51,7 @@ def is50or60(
alt_ref (float): reference altitude (ADS-B altitude), ft alt_ref (float): reference altitude (ADS-B altitude), ft
Returns: Returns:
String or None: BDS version, or possible versions, String or None: BDS version, or possible versions, or None if nothing matches.
or None if nothing matches.
""" """
@ -114,17 +113,15 @@ def is50or60(
return BDS return BDS
def infer(msg: str, mrar: bool = False) -> Optional[str]: def infer(msg, mrar=False):
"""Estimate the most likely BDS code of an message. """Estimate the most likely BDS code of an message.
Args: Args:
msg (str): 28 hexdigits string msg (str): 28 hexdigits string
mrar (bool): Also infer MRAR (BDS 44) and MHR (BDS 45). mrar (bool): Also infer MRAR (BDS 44) and MHR (BDS 45). Defaults to False.
Defaults to False.
Returns: Returns:
String or None: BDS version, or possible versions, String or None: BDS version, or possible versions, or None if nothing matches.
or None if nothing matches.
""" """
df = common.df(msg) df = common.df(msg)
@ -135,8 +132,6 @@ def infer(msg: str, mrar: bool = False) -> Optional[str]:
# For ADS-B / Mode-S extended squitter # For ADS-B / Mode-S extended squitter
if df == 17: if df == 17:
tc = common.typecode(msg) tc = common.typecode(msg)
if tc is None:
return None
if 1 <= tc <= 4: if 1 <= tc <= 4:
return "BDS08" # identification and category return "BDS08" # identification and category

View File

@ -1,20 +1,14 @@
# ------------------------------------------ # ------------------------------------------
# BDS 0,5 # BDS 0,5
# ADS-B TC=9-18 # ADS-B TC=9-18
# Airborne position # Airborn position
# ------------------------------------------ # ------------------------------------------
from __future__ import annotations from pyModeS import common
from datetime import datetime
from ... import common
def airborne_position( def airborne_position(msg0, msg1, t0, t1):
msg0: str, msg1: str, t0: int | datetime, t1: int | datetime """Decode airborn position from a pair of even and odd position message
) -> None | tuple[float, float]:
"""Decode airborne position from a pair of even and odd position message
Args: Args:
msg0 (string): even message (28 hexdigits) msg0 (string): even message (28 hexdigits)
@ -40,13 +34,13 @@ def airborne_position(
raise RuntimeError("Both even and odd CPR frames are required.") raise RuntimeError("Both even and odd CPR frames are required.")
# 131072 is 2^17, since CPR lat and lon are 17 bits each. # 131072 is 2^17, since CPR lat and lon are 17 bits each.
cprlat_even = common.bin2int(mb0[22:39]) / 131072 cprlat_even = common.bin2int(mb0[22:39]) / 131072.0
cprlon_even = common.bin2int(mb0[39:56]) / 131072 cprlon_even = common.bin2int(mb0[39:56]) / 131072.0
cprlat_odd = common.bin2int(mb1[22:39]) / 131072 cprlat_odd = common.bin2int(mb1[22:39]) / 131072.0
cprlon_odd = common.bin2int(mb1[39:56]) / 131072 cprlon_odd = common.bin2int(mb1[39:56]) / 131072.0
air_d_lat_even = 360 / 60 air_d_lat_even = 360.0 / 60
air_d_lat_odd = 360 / 59 air_d_lat_odd = 360.0 / 59
# compute latitude index 'j' # compute latitude index 'j'
j = common.floor(59 * cprlat_even - 60 * cprlat_odd + 0.5) j = common.floor(59 * cprlat_even - 60 * cprlat_odd + 0.5)
@ -65,33 +59,30 @@ def airborne_position(
return None return None
# compute ni, longitude index m, and longitude # compute ni, longitude index m, and longitude
# (people pass int+int or datetime+datetime) if t0 > t1:
if t0 > t1: # type: ignore
lat = lat_even lat = lat_even
nl = common.cprNL(lat) nl = common.cprNL(lat)
ni = max(common.cprNL(lat) - 0, 1) ni = max(common.cprNL(lat) - 0, 1)
m = common.floor(cprlon_even * (nl - 1) - cprlon_odd * nl + 0.5) m = common.floor(cprlon_even * (nl - 1) - cprlon_odd * nl + 0.5)
lon = (360 / ni) * (m % ni + cprlon_even) lon = (360.0 / ni) * (m % ni + cprlon_even)
else: else:
lat = lat_odd lat = lat_odd
nl = common.cprNL(lat) nl = common.cprNL(lat)
ni = max(common.cprNL(lat) - 1, 1) ni = max(common.cprNL(lat) - 1, 1)
m = common.floor(cprlon_even * (nl - 1) - cprlon_odd * nl + 0.5) m = common.floor(cprlon_even * (nl - 1) - cprlon_odd * nl + 0.5)
lon = (360 / ni) * (m % ni + cprlon_odd) lon = (360.0 / ni) * (m % ni + cprlon_odd)
if lon > 180: if lon > 180:
lon = lon - 360 lon = lon - 360
return lat, lon return round(lat, 5), round(lon, 5)
def airborne_position_with_ref( def airborne_position_with_ref(msg, lat_ref, lon_ref):
msg: str, lat_ref: float, lon_ref: float
) -> tuple[float, float]:
"""Decode airborne position with only one message, """Decode airborne position with only one message,
knowing reference nearby location, such as previously calculated location, knowing reference nearby location, such as previously calculated location,
ground station, or airport location, etc. The reference position shall ground station, or airport location, etc. The reference position shall
be within 180NM of the true position. be with in 180NM of the true position.
Args: Args:
msg (str): even message (28 hexdigits) msg (str): even message (28 hexdigits)
@ -104,11 +95,11 @@ def airborne_position_with_ref(
mb = common.hex2bin(msg)[32:] mb = common.hex2bin(msg)[32:]
cprlat = common.bin2int(mb[22:39]) / 131072 cprlat = common.bin2int(mb[22:39]) / 131072.0
cprlon = common.bin2int(mb[39:56]) / 131072 cprlon = common.bin2int(mb[39:56]) / 131072.0
i = int(mb[21]) i = int(mb[21])
d_lat = 360 / 59 if i else 360 / 60 d_lat = 360.0 / 59 if i else 360.0 / 60
j = common.floor(lat_ref / d_lat) + common.floor( j = common.floor(lat_ref / d_lat) + common.floor(
0.5 + ((lat_ref % d_lat) / d_lat) - cprlat 0.5 + ((lat_ref % d_lat) / d_lat) - cprlat
@ -119,9 +110,9 @@ def airborne_position_with_ref(
ni = common.cprNL(lat) - i ni = common.cprNL(lat) - i
if ni > 0: if ni > 0:
d_lon = 360 / ni d_lon = 360.0 / ni
else: else:
d_lon = 360 d_lon = 360.0
m = common.floor(lon_ref / d_lon) + common.floor( m = common.floor(lon_ref / d_lon) + common.floor(
0.5 + ((lon_ref % d_lon) / d_lon) - cprlon 0.5 + ((lon_ref % d_lon) / d_lon) - cprlon
@ -129,10 +120,10 @@ def airborne_position_with_ref(
lon = d_lon * (m + cprlon) lon = d_lon * (m + cprlon)
return lat, lon return round(lat, 5), round(lon, 5)
def altitude(msg: str) -> None | int: def altitude(msg):
"""Decode aircraft altitude """Decode aircraft altitude
Args: Args:
@ -144,19 +135,17 @@ def altitude(msg: str) -> None | int:
tc = common.typecode(msg) tc = common.typecode(msg)
if tc is None or tc < 9 or tc == 19 or tc > 22: if tc < 9 or tc == 19 or tc > 22:
raise RuntimeError("%s: Not an airborne position message" % msg) raise RuntimeError("%s: Not a airborn position message" % msg)
mb = common.hex2bin(msg)[32:] mb = common.hex2bin(msg)[32:]
altbin = mb[8:20] altbin = mb[8:20]
if tc < 19: if tc < 19:
altcode = altbin[0:6] + "0" + altbin[6:] altcode = altbin[0:6] + "0" + altbin[6:]
else:
altcode = altbin[0:6] + "0" + altbin[6:]
alt = common.altitude(altcode) alt = common.altitude(altcode)
if alt != -999999:
return alt return alt
else:
# return None if altitude is invalid
return None
else:
return common.bin2int(altbin) * 3.28084 # type: ignore

View File

@ -1,24 +1,13 @@
# ------------------------------------------ # ------------------------------------------
# BDS 0,6 # BDS 0,6
# ADS-B TC=5-8 # ADS-B TC=5-8
# Surface movement # Surface movment
# ------------------------------------------ # ------------------------------------------
from __future__ import annotations from pyModeS import common
from datetime import datetime
from ... import common
def surface_position( def surface_position(msg0, msg1, t0, t1, lat_ref, lon_ref):
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, """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. the lat/lon of receiver must be provided to yield the correct solution.
@ -38,13 +27,13 @@ def surface_position(
msgbin1 = common.hex2bin(msg1) msgbin1 = common.hex2bin(msg1)
# 131072 is 2^17, since CPR lat and lon are 17 bits each. # 131072 is 2^17, since CPR lat and lon are 17 bits each.
cprlat_even = common.bin2int(msgbin0[54:71]) / 131072 cprlat_even = common.bin2int(msgbin0[54:71]) / 131072.0
cprlon_even = common.bin2int(msgbin0[71:88]) / 131072 cprlon_even = common.bin2int(msgbin0[71:88]) / 131072.0
cprlat_odd = common.bin2int(msgbin1[54:71]) / 131072 cprlat_odd = common.bin2int(msgbin1[54:71]) / 131072.0
cprlon_odd = common.bin2int(msgbin1[71:88]) / 131072 cprlon_odd = common.bin2int(msgbin1[71:88]) / 131072.0
air_d_lat_even = 90 / 60 air_d_lat_even = 90.0 / 60
air_d_lat_odd = 90 / 59 air_d_lat_odd = 90.0 / 59
# compute latitude index 'j' # compute latitude index 'j'
j = common.floor(59 * cprlat_even - 60 * cprlat_odd + 0.5) j = common.floor(59 * cprlat_even - 60 * cprlat_odd + 0.5)
@ -54,8 +43,8 @@ def surface_position(
lat_odd_n = float(air_d_lat_odd * (j % 59 + cprlat_odd)) lat_odd_n = float(air_d_lat_odd * (j % 59 + cprlat_odd))
# solution for north hemisphere # solution for north hemisphere
lat_even_s = lat_even_n - 90 lat_even_s = lat_even_n - 90.0
lat_odd_s = lat_odd_n - 90 lat_odd_s = lat_odd_n - 90.0
# chose which solution corrispondes to receiver location # chose which solution corrispondes to receiver location
lat_even = lat_even_n if lat_ref > 0 else lat_even_s lat_even = lat_even_n if lat_ref > 0 else lat_even_s
@ -66,41 +55,38 @@ def surface_position(
return None return None
# compute ni, longitude index m, and longitude # compute ni, longitude index m, and longitude
# (people pass int+int or datetime+datetime) if t0 > t1:
if t0 > t1: # type: ignore
lat = lat_even lat = lat_even
nl = common.cprNL(lat_even) nl = common.cprNL(lat_even)
ni = max(common.cprNL(lat_even) - 0, 1) ni = max(common.cprNL(lat_even) - 0, 1)
m = common.floor(cprlon_even * (nl - 1) - cprlon_odd * nl + 0.5) m = common.floor(cprlon_even * (nl - 1) - cprlon_odd * nl + 0.5)
lon = (90 / ni) * (m % ni + cprlon_even) lon = (90.0 / ni) * (m % ni + cprlon_even)
else: else:
lat = lat_odd lat = lat_odd
nl = common.cprNL(lat_odd) nl = common.cprNL(lat_odd)
ni = max(common.cprNL(lat_odd) - 1, 1) ni = max(common.cprNL(lat_odd) - 1, 1)
m = common.floor(cprlon_even * (nl - 1) - cprlon_odd * nl + 0.5) m = common.floor(cprlon_even * (nl - 1) - cprlon_odd * nl + 0.5)
lon = (90 / ni) * (m % ni + cprlon_odd) lon = (90.0 / ni) * (m % ni + cprlon_odd)
# four possible longitude solutions # four possible longitude solutions
lons = [lon, lon + 90, lon + 180, lon + 270] lons = [lon, lon + 90.0, lon + 180.0, lon + 270.0]
# make sure lons are between -180 and 180 # make sure lons are between -180 and 180
lons = [(lon + 180) % 360 - 180 for lon in lons] lons = [(l + 180) % 360 - 180 for l in lons]
# the closest solution to receiver is the correct one # the closest solution to receiver is the correct one
dls = [abs(lon_ref - lon) for lon in lons] dls = [abs(lon_ref - l) for l in lons]
imin = min(range(4), key=dls.__getitem__) imin = min(range(4), key=dls.__getitem__)
lon = lons[imin] lon = lons[imin]
return lat, lon return round(lat, 5), round(lon, 5)
def surface_position_with_ref( def surface_position_with_ref(msg, lat_ref, lon_ref):
msg: str, lat_ref: float, lon_ref: float
) -> tuple[float, float]:
"""Decode surface position with only one message, """Decode surface position with only one message,
knowing reference nearby location, such as previously calculated location, knowing reference nearby location, such as previously calculated location,
ground station, or airport location, etc. The reference position shall ground station, or airport location, etc. The reference position shall
be within 45NM of the true position. be with in 45NM of the true position.
Args: Args:
msg (str): even message (28 hexdigits) msg (str): even message (28 hexdigits)
@ -113,11 +99,11 @@ def surface_position_with_ref(
mb = common.hex2bin(msg)[32:] mb = common.hex2bin(msg)[32:]
cprlat = common.bin2int(mb[22:39]) / 131072 cprlat = common.bin2int(mb[22:39]) / 131072.0
cprlon = common.bin2int(mb[39:56]) / 131072 cprlon = common.bin2int(mb[39:56]) / 131072.0
i = int(mb[21]) i = int(mb[21])
d_lat = 90 / 59 if i else 90 / 60 d_lat = 90.0 / 59 if i else 90.0 / 60
j = common.floor(lat_ref / d_lat) + common.floor( j = common.floor(lat_ref / d_lat) + common.floor(
0.5 + ((lat_ref % d_lat) / d_lat) - cprlat 0.5 + ((lat_ref % d_lat) / d_lat) - cprlat
@ -128,9 +114,9 @@ def surface_position_with_ref(
ni = common.cprNL(lat) - i ni = common.cprNL(lat) - i
if ni > 0: if ni > 0:
d_lon = 90 / ni d_lon = 90.0 / ni
else: else:
d_lon = 90 d_lon = 90.0
m = common.floor(lon_ref / d_lon) + common.floor( m = common.floor(lon_ref / d_lon) + common.floor(
0.5 + ((lon_ref % d_lon) / d_lon) - cprlon 0.5 + ((lon_ref % d_lon) / d_lon) - cprlon
@ -138,22 +124,19 @@ def surface_position_with_ref(
lon = d_lon * (m + cprlon) lon = d_lon * (m + cprlon)
return lat, lon return round(lat, 5), round(lon, 5)
def surface_velocity( def surface_velocity(msg, source=False):
msg: str, source: bool = False
) -> tuple[None | float, None | float, int, str]:
"""Decode surface velocity from a surface position message """Decode surface velocity from a surface position message
Args: Args:
msg (str): 28 hexdigits string msg (str): 28 hexdigits string
source (boolean): Include direction and vertical rate sources in return. source (boolean): Include direction and vertical rate sources in return. Default to False.
Default to False.
If set to True, the function will return six value instead of four. If set to True, the function will return six value instead of four.
Returns: Returns:
int, float, int, string, [string], [string]: int, float, int, string, [string], [string]: Four or six parameters, including:
- Speed (kt) - Speed (kt)
- Angle (degree), ground track - Angle (degree), ground track
- Vertical rate, always 0 - Vertical rate, always 0
@ -162,8 +145,7 @@ def surface_velocity(
- [Optional] Vertical rate source (None) - [Optional] Vertical rate source (None)
""" """
tc = common.typecode(msg) if common.typecode(msg) < 5 or common.typecode(msg) > 8:
if tc is None or tc < 5 or tc > 8:
raise RuntimeError("%s: Not a surface message, expecting 5<TC<8" % msg) raise RuntimeError("%s: Not a surface message, expecting 5<TC<8" % msg)
mb = common.hex2bin(msg)[32:] mb = common.hex2bin(msg)[32:]
@ -171,7 +153,8 @@ def surface_velocity(
# ground track # ground track
trk_status = int(mb[12]) trk_status = int(mb[12])
if trk_status == 1: if trk_status == 1:
trk = common.bin2int(mb[13:20]) * 360 / 128 trk = common.bin2int(mb[13:20]) * 360.0 / 128.0
trk = round(trk, 1)
else: else:
trk = None trk = None
@ -181,17 +164,16 @@ def surface_velocity(
if mov == 0 or mov > 124: if mov == 0 or mov > 124:
spd = None spd = None
elif mov == 1: elif mov == 1:
spd = 0.0 spd = 0
elif mov == 124: elif mov == 124:
spd = 175.0 spd = 175
else: else:
mov_lb = [2, 9, 13, 39, 94, 109, 124] mov_lb = [2, 9, 13, 39, 94, 109, 124]
kts_lb: list[float] = [0.125, 1, 2, 15, 70, 100, 175] kts_lb = [0.125, 1, 2, 15, 70, 100, 175]
step: list[float] = [0.125, 0.25, 0.5, 1, 2, 5] step = [0.125, 0.25, 0.5, 1, 2, 5]
i = next(m[0] for m in enumerate(mov_lb) if m[1] > mov) 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] spd = kts_lb[i - 1] + (mov - mov_lb[i - 1]) * step[i - 1]
if source: if source:
return spd, trk, 0, "GS", "TRUE_NORTH", None # type: ignore return spd, trk, 0, "GS", "TRUE_NORTH", None
else: else:
return spd, trk, 0, "GS" return spd, trk, 0, "GS"

View File

@ -1,13 +1,13 @@
# ------------------------------------------ # ------------------------------------------
# BDS 0,8 # BDS 0,8
# ADS-B TC=1-4 # ADS-B TC=1-4
# Aircraft identification and category # Aircraft identitification and category
# ------------------------------------------ # ------------------------------------------
from ... import common from pyModeS import common
def category(msg: str) -> int: def category(msg):
"""Aircraft category number """Aircraft category number
Args: Args:
@ -17,8 +17,7 @@ def category(msg: str) -> int:
int: category number int: category number
""" """
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) raise RuntimeError("%s: Not a identification message" % msg)
msgbin = common.hex2bin(msg) msgbin = common.hex2bin(msg)
@ -26,7 +25,7 @@ def category(msg: str) -> int:
return common.bin2int(mebin[5:8]) return common.bin2int(mebin[5:8])
def callsign(msg: str) -> str: def callsign(msg):
"""Aircraft callsign """Aircraft callsign
Args: Args:
@ -35,9 +34,8 @@ def callsign(msg: str) -> str:
Returns: Returns:
string: callsign string: callsign
""" """
tc = common.typecode(msg)
if tc is None or tc < 1 or tc > 4: if common.typecode(msg) < 1 or common.typecode(msg) > 4:
raise RuntimeError("%s: Not a identification message" % msg) raise RuntimeError("%s: Not a identification message" % msg)
chars = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ#####_###############0123456789######" chars = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ#####_###############0123456789######"

View File

@ -1,41 +1,35 @@
# ------------------------------------------ # ------------------------------------------
# BDS 0,9 # BDS 0,9
# ADS-B TC=19 # ADS-B TC=19
# Aircraft Airborne velocity # Aircraft Airborn velocity
# ------------------------------------------ # ------------------------------------------
from __future__ import annotations from pyModeS import common
import math 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. """Decode airborne velocity.
Args: Args:
msg (str): 28 hexdigits string msg (str): 28 hexdigits string
source (boolean): Include direction and vertical rate sources in return. source (boolean): Include direction and vertical rate sources in return. Default to False.
Default to False.
If set to True, the function will return six value instead of four. If set to True, the function will return six value instead of four.
Returns: Returns:
int, float, int, string, [string], [string]: int, float, int, string, [string], [string]: Four or six parameters, including:
- Speed (kt) - Speed (kt)
- Angle (degree), either ground track or heading - Angle (degree), either ground track or heading
- Vertical rate (ft/min) - Vertical rate (ft/min)
- Speed type ('GS' for ground speed, 'AS' for airspeed) - Speed type ('GS' for ground speed, 'AS' for airspeed)
- [Optional] Direction source ('TRUE_NORTH' or 'MAGNETIC_NORTH') - [Optional] Direction source ('TRUE_NORTH' or 'MAGENTIC_NORTH')
- [Optional] Vertical rate source ('BARO' or 'GNSS') - [Optional] Vertical rate source ('BARO' or 'GNSS')
""" """
if common.typecode(msg) != 19: if common.typecode(msg) != 19:
raise RuntimeError( raise RuntimeError("%s: Not a airborne velocity message, expecting TC=19" % msg)
"%s: Not a airborne velocity message, expecting TC=19" % msg
)
mb = common.hex2bin(msg)[32:] mb = common.hex2bin(msg)[32:]
@ -44,25 +38,14 @@ def airborne_velocity(
if common.bin2int(mb[14:24]) == 0 or common.bin2int(mb[25:35]) == 0: if common.bin2int(mb[14:24]) == 0 or common.bin2int(mb[25:35]) == 0:
return None return None
trk_or_hdg: None | float
spd: None | float
if subtype in (1, 2): if subtype in (1, 2):
v_ew = common.bin2int(mb[14:24])
v_ns = common.bin2int(mb[25:35])
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_sign = -1 if mb[13] == "1" else 1
v_ew = v_ew - 1 # east-west velocity v_ew = common.bin2int(mb[14:24]) - 1 # east-west velocity
if subtype == 2: # Supersonic if subtype == 2: # Supersonic
v_ew *= 4 v_ew *= 4
v_ns_sign = -1 if mb[24] == "1" else 1 v_ns_sign = -1 if mb[24] == "1" else 1
v_ns = v_ns - 1 # north-south velocity v_ns = common.bin2int(mb[25:35]) - 1 # north-south velocity
if subtype == 2: # Supersonic if subtype == 2: # Supersonic
v_ns *= 4 v_ns *= 4
@ -76,22 +59,22 @@ def airborne_velocity(
trk = math.degrees(trk) # convert to degrees trk = math.degrees(trk) # convert to degrees
trk = trk if trk >= 0 else trk + 360 # no negative val trk = trk if trk >= 0 else trk + 360 # no negative val
trk_or_hdg = trk
spd_type = "GS" spd_type = "GS"
trk_or_hdg = round(trk, 2)
dir_type = "TRUE_NORTH" dir_type = "TRUE_NORTH"
else: else:
if mb[13] == "0": if mb[13] == "0":
hdg = None hdg = None
else: else:
hdg = common.bin2int(mb[14:24]) / 1024 * 360.0 hdg = common.bin2int(mb[14:24]) / 1024.0 * 360.0
hdg = round(hdg, 2)
trk_or_hdg = hdg trk_or_hdg = hdg
spd = common.bin2int(mb[25:35]) spd = common.bin2int(mb[25:35])
spd = None if spd == 0 else spd - 1 spd = None if spd == 0 else spd - 1
if subtype == 4 and spd is not None: # Supersonic if subtype == 4: # Supersonic
spd *= 4 spd *= 4
if mb[24] == "0": if mb[24] == "0":
@ -99,7 +82,7 @@ def airborne_velocity(
else: else:
spd_type = "TAS" spd_type = "TAS"
dir_type = "MAGNETIC_NORTH" dir_type = "MAGENTIC_NORTH"
vr_source = "GNSS" if mb[35] == "0" else "BARO" vr_source = "GNSS" if mb[35] == "0" else "BARO"
vr_sign = -1 if mb[36] == "1" else 1 vr_sign = -1 if mb[36] == "1" else 1
@ -107,19 +90,12 @@ def airborne_velocity(
vs = None if vr == 0 else int(vr_sign * (vr - 1) * 64) vs = None if vr == 0 else int(vr_sign * (vr - 1) * 64)
if source: if source:
return ( # type: ignore return spd, trk_or_hdg, vs, spd_type, dir_type, vr_source
spd,
trk_or_hdg,
vs,
spd_type,
dir_type,
vr_source,
)
else: else:
return spd, trk_or_hdg, vs, spd_type return spd, trk_or_hdg, vs, spd_type
def altitude_diff(msg: str) -> None | float: def altitude_diff(msg):
"""Decode the differece between GNSS and barometric altitude. """Decode the differece between GNSS and barometric altitude.
Args: Args:
@ -132,10 +108,8 @@ def altitude_diff(msg: str) -> None | float:
""" """
tc = common.typecode(msg) tc = common.typecode(msg)
if tc is None or tc != 19: if tc != 19:
raise RuntimeError( raise RuntimeError("%s: Not a airborne velocity message, expecting TC=19" % msg)
"%s: Not a airborne velocity message, expecting TC=19" % msg
)
msgbin = common.hex2bin(msg) msgbin = common.hex2bin(msg)
sign = -1 if int(msgbin[80]) else 1 sign = -1 if int(msgbin[80]) else 1

View File

@ -3,11 +3,10 @@
# Data link capability report # Data link capability report
# ------------------------------------------ # ------------------------------------------
from pyModeS import common
from ... import common
def is10(msg: str) -> bool: def is10(msg):
"""Check if a message is likely to be BDS code 1,0 """Check if a message is likely to be BDS code 1,0
Args: Args:
@ -39,7 +38,7 @@ def is10(msg: str) -> bool:
return True return True
def ovc10(msg: str) -> int: def ovc10(msg):
"""Return the overlay control capability """Return the overlay control capability
Args: Args:

View File

@ -3,12 +3,10 @@
# Common usage GICB capability report # Common usage GICB capability report
# ------------------------------------------ # ------------------------------------------
from typing import List from pyModeS import common
from ... import common
def is17(msg: str) -> bool: def is17(msg):
"""Check if a message is likely to be BDS code 1,7 """Check if a message is likely to be BDS code 1,7
Args: Args:
@ -40,14 +38,14 @@ def is17(msg: str) -> bool:
return True return True
def cap17(msg: str) -> List[str]: def cap17(msg):
"""Extract capacities from BDS 1,7 message """Extract capacities from BDS 1,7 message
Args: Args:
msg (str): 28 hexdigits string msg (str): 28 hexdigits string
Returns: Returns:
list: list of supported BDS codes list: list of support BDS codes
""" """
allbds = [ allbds = [
"05", "05",

View File

@ -3,10 +3,10 @@
# Aircraft identification # Aircraft identification
# ------------------------------------------ # ------------------------------------------
from ... import common from pyModeS import common
def is20(msg: str) -> bool: def is20(msg):
"""Check if a message is likely to be BDS code 2,0 """Check if a message is likely to be BDS code 2,0
Args: Args:
@ -24,17 +24,15 @@ def is20(msg: str) -> bool:
if d[0:8] != "00100000": if d[0:8] != "00100000":
return False return False
# allow empty callsign cs = cs20(msg)
if common.bin2int(d[8:56]) == 0:
return True
if "#" in cs20(msg): if "#" in cs:
return False return False
return True return True
def cs20(msg: str) -> str: def cs20(msg):
"""Aircraft callsign """Aircraft callsign
Args: Args:

View File

@ -3,11 +3,11 @@
# ACAS active resolution advisory # ACAS active resolution advisory
# ------------------------------------------ # ------------------------------------------
from ... import common from pyModeS import common
def is30(msg: str) -> bool: def is30(msg):
"""Check if a message is likely to be BDS code 3,0 """Check if a message is likely to be BDS code 2,0
Args: Args:
msg (str): 28 hexdigits string msg (str): 28 hexdigits string

View File

@ -4,12 +4,10 @@
# ------------------------------------------ # ------------------------------------------
import warnings import warnings
from typing import Optional from pyModeS import common
from ... import common
def is40(msg: str) -> bool: def is40(msg):
"""Check if a message is likely to be BDS code 4,0 """Check if a message is likely to be BDS code 4,0
Args: Args:
@ -52,7 +50,7 @@ def is40(msg: str) -> bool:
return True return True
def selalt40mcp(msg: str) -> Optional[int]: def selalt40mcp(msg):
"""Selected altitude, MCP/FCU """Selected altitude, MCP/FCU
Args: Args:
@ -70,7 +68,7 @@ def selalt40mcp(msg: str) -> Optional[int]:
return alt return alt
def selalt40fms(msg: str) -> Optional[int]: def selalt40fms(msg):
"""Selected altitude, FMS """Selected altitude, FMS
Args: Args:
@ -88,7 +86,7 @@ def selalt40fms(msg: str) -> Optional[int]:
return alt return alt
def p40baro(msg: str) -> Optional[float]: def p40baro(msg):
"""Barometric pressure setting """Barometric pressure setting
Args: Args:
@ -106,19 +104,17 @@ def p40baro(msg: str) -> Optional[float]:
return p return p
def alt40mcp(msg: str) -> Optional[int]: def alt40mcp(msg):
warnings.warn( warnings.warn(
"""alt40mcp() has been renamed to selalt40mcp(). "alt40mcp() has been renamed to selalt40mcp(). It will be removed in the future.",
It will be removed in the future.""",
DeprecationWarning, DeprecationWarning,
) )
return selalt40mcp(msg) return selalt40mcp(msg)
def alt40fms(msg: str) -> Optional[int]: def alt40fms(msg):
warnings.warn( warnings.warn(
"""alt40fms() has been renamed to selalt40fms(). "alt40fms() has been renamed to selalt40fms(). It will be removed in the future.",
It will be removed in the future.""",
DeprecationWarning, DeprecationWarning,
) )
return selalt40fms(msg) return selalt40fms(msg)

View File

@ -3,12 +3,10 @@
# Meteorological routine air report # Meteorological routine air report
# ------------------------------------------ # ------------------------------------------
from typing import Optional, Tuple from pyModeS import common
from ... import common
def is44(msg: str) -> bool: def is44(msg):
"""Check if a message is likely to be BDS code 4,4. """Check if a message is likely to be BDS code 4,4.
Meteorological routine air report Meteorological routine air report
@ -53,7 +51,7 @@ def is44(msg: str) -> bool:
return True return True
def wind44(msg: str) -> Tuple[Optional[int], Optional[float]]: def wind44(msg):
"""Wind speed and direction. """Wind speed and direction.
Args: Args:
@ -70,12 +68,12 @@ def wind44(msg: str) -> Tuple[Optional[int], Optional[float]]:
return None, None return None, None
speed = common.bin2int(d[5:14]) # knots speed = common.bin2int(d[5:14]) # knots
direction = common.bin2int(d[14:23]) * 180 / 256 # degree direction = common.bin2int(d[14:23]) * 180.0 / 256.0 # degree
return speed, direction return round(speed, 0), round(direction, 1)
def temp44(msg: str) -> Tuple[float, float]: def temp44(msg):
"""Static air temperature. """Static air temperature.
Args: Args:
@ -96,13 +94,15 @@ def temp44(msg: str) -> Tuple[float, float]:
value = value - 1024 value = value - 1024
temp = value * 0.25 # celsius temp = value * 0.25 # celsius
temp = round(temp, 2)
temp_alternative = value * 0.125 # celsius temp_alternative = value * 0.125 # celsius
temp_alternative = round(temp_alternative, 3)
return temp, temp_alternative return temp, temp_alternative
def p44(msg: str) -> Optional[int]: def p44(msg):
"""Static pressure. """Static pressure.
Args: Args:
@ -122,7 +122,7 @@ def p44(msg: str) -> Optional[int]:
return p return p
def hum44(msg: str) -> Optional[float]: def hum44(msg):
"""humidity """humidity
Args: Args:
@ -136,13 +136,13 @@ def hum44(msg: str) -> Optional[float]:
if d[49] == "0": if d[49] == "0":
return None return None
hm = common.bin2int(d[50:56]) * 100 / 64 # % hm = common.bin2int(d[50:56]) * 100.0 / 64 # %
return hm return round(hm, 1)
def turb44(msg: str) -> Optional[int]: def turb44(msg):
"""Turbulence. """Turblence.
Args: Args:
msg (str): 28 hexdigits string msg (str): 28 hexdigits string

View File

@ -3,12 +3,10 @@
# Meteorological hazard report # Meteorological hazard report
# ------------------------------------------ # ------------------------------------------
from typing import Optional from pyModeS import common
from ... import common
def is45(msg: str) -> bool: def is45(msg):
"""Check if a message is likely to be BDS code 4,5. """Check if a message is likely to be BDS code 4,5.
Meteorological hazard report Meteorological hazard report
@ -62,7 +60,7 @@ def is45(msg: str) -> bool:
return True return True
def turb45(msg: str) -> Optional[int]: def turb45(msg):
"""Turbulence. """Turbulence.
Args: Args:
@ -80,7 +78,7 @@ def turb45(msg: str) -> Optional[int]:
return turb return turb
def ws45(msg: str) -> Optional[int]: def ws45(msg):
"""Wind shear. """Wind shear.
Args: Args:
@ -98,7 +96,7 @@ def ws45(msg: str) -> Optional[int]:
return ws return ws
def mb45(msg: str) -> Optional[int]: def mb45(msg):
"""Microburst. """Microburst.
Args: Args:
@ -116,7 +114,7 @@ def mb45(msg: str) -> Optional[int]:
return mb return mb
def ic45(msg: str) -> Optional[int]: def ic45(msg):
"""Icing. """Icing.
Args: Args:
@ -134,7 +132,7 @@ def ic45(msg: str) -> Optional[int]:
return ic return ic
def wv45(msg: str) -> Optional[int]: def wv45(msg):
"""Wake vortex. """Wake vortex.
Args: Args:
@ -152,7 +150,7 @@ def wv45(msg: str) -> Optional[int]:
return ws return ws
def temp45(msg: str) -> Optional[float]: def temp45(msg):
"""Static air temperature. """Static air temperature.
Args: Args:
@ -171,11 +169,12 @@ def temp45(msg: str) -> Optional[float]:
value = value - 512 value = value - 512
temp = value * 0.25 # celsius temp = value * 0.25 # celsius
temp = round(temp, 1)
return temp return temp
def p45(msg: str) -> Optional[int]: def p45(msg):
"""Average static pressure. """Average static pressure.
Args: Args:
@ -192,7 +191,7 @@ def p45(msg: str) -> Optional[int]:
return p return p
def rh45(msg: str) -> Optional[int]: def rh45(msg):
"""Radio height. """Radio height.
Args: Args:

View File

@ -3,12 +3,10 @@
# Track and turn report # Track and turn report
# ------------------------------------------ # ------------------------------------------
from typing import Optional from pyModeS import common
from ... import common
def is50(msg: str) -> bool: def is50(msg):
"""Check if a message is likely to be BDS code 5,0 """Check if a message is likely to be BDS code 5,0
(Track and turn report) (Track and turn report)
@ -59,7 +57,7 @@ def is50(msg: str) -> bool:
return True return True
def roll50(msg: str) -> Optional[float]: def roll50(msg):
"""Roll angle, BDS 5,0 message """Roll angle, BDS 5,0 message
Args: Args:
@ -80,11 +78,11 @@ def roll50(msg: str) -> Optional[float]:
if sign: if sign:
value = value - 512 value = value - 512
angle = value * 45 / 256 # degree angle = value * 45.0 / 256.0 # degree
return angle return round(angle, 1)
def trk50(msg: str) -> Optional[float]: def trk50(msg):
"""True track angle, BDS 5,0 message """True track angle, BDS 5,0 message
Args: Args:
@ -104,16 +102,16 @@ def trk50(msg: str) -> Optional[float]:
if sign: if sign:
value = value - 1024 value = value - 1024
trk = value * 90 / 512.0 trk = value * 90.0 / 512.0
# convert from [-180, 180] to [0, 360] # convert from [-180, 180] to [0, 360]
if trk < 0: if trk < 0:
trk = 360 + trk trk = 360 + trk
return trk return round(trk, 3)
def gs50(msg: str) -> Optional[float]: def gs50(msg):
"""Ground speed, BDS 5,0 message """Ground speed, BDS 5,0 message
Args: Args:
@ -131,7 +129,7 @@ def gs50(msg: str) -> Optional[float]:
return spd return spd
def rtrk50(msg: str) -> Optional[float]: def rtrk50(msg):
"""Track angle rate, BDS 5,0 message """Track angle rate, BDS 5,0 message
Args: Args:
@ -153,11 +151,11 @@ def rtrk50(msg: str) -> Optional[float]:
if sign: if sign:
value = value - 512 value = value - 512
angle = value * 8 / 256 # degree / sec angle = value * 8.0 / 256.0 # degree / sec
return angle return round(angle, 3)
def tas50(msg: str) -> Optional[float]: def tas50(msg):
"""Aircraft true airspeed, BDS 5,0 message """Aircraft true airspeed, BDS 5,0 message
Args: Args:

View File

@ -3,12 +3,10 @@
# Air-referenced state vector # Air-referenced state vector
# ------------------------------------------ # ------------------------------------------
from typing import Optional from pyModeS import common
from ... import common
def is53(msg: str) -> bool: def is53(msg):
"""Check if a message is likely to be BDS code 5,3 """Check if a message is likely to be BDS code 5,3
(Air-referenced state vector) (Air-referenced state vector)
@ -60,7 +58,7 @@ def is53(msg: str) -> bool:
return True return True
def hdg53(msg: str) -> Optional[float]: def hdg53(msg):
"""Magnetic heading, BDS 5,3 message """Magnetic heading, BDS 5,3 message
Args: Args:
@ -80,16 +78,16 @@ def hdg53(msg: str) -> Optional[float]:
if sign: if sign:
value = value - 1024 value = value - 1024
hdg = value * 90 / 512 # degree hdg = value * 90.0 / 512.0 # degree
# convert from [-180, 180] to [0, 360] # convert from [-180, 180] to [0, 360]
if hdg < 0: if hdg < 0:
hdg = 360 + hdg hdg = 360 + hdg
return hdg return round(hdg, 3)
def ias53(msg: str) -> Optional[float]: def ias53(msg):
"""Indicated airspeed, DBS 5,3 message """Indicated airspeed, DBS 5,3 message
Args: Args:
@ -107,7 +105,7 @@ def ias53(msg: str) -> Optional[float]:
return ias return ias
def mach53(msg: str) -> Optional[float]: def mach53(msg):
"""MACH number, DBS 5,3 message """MACH number, DBS 5,3 message
Args: Args:
@ -122,10 +120,10 @@ def mach53(msg: str) -> Optional[float]:
return None return None
mach = common.bin2int(d[24:33]) * 0.008 mach = common.bin2int(d[24:33]) * 0.008
return mach return round(mach, 3)
def tas53(msg: str) -> Optional[float]: def tas53(msg):
"""Aircraft true airspeed, BDS 5,3 message """Aircraft true airspeed, BDS 5,3 message
Args: Args:
@ -140,10 +138,10 @@ def tas53(msg: str) -> Optional[float]:
return None return None
tas = common.bin2int(d[34:46]) * 0.5 # kts tas = common.bin2int(d[34:46]) * 0.5 # kts
return tas return round(tas, 1)
def vr53(msg: str) -> Optional[int]: def vr53(msg):
"""Vertical rate """Vertical rate
Args: Args:

View File

@ -3,13 +3,11 @@
# Heading and speed report # Heading and speed report
# ------------------------------------------ # ------------------------------------------
from typing import Optional from pyModeS import common
from pyModeS.extra import aero
from ... import common
from ...extra import aero
def is60(msg: str) -> bool: def is60(msg):
"""Check if a message is likely to be BDS code 6,0 """Check if a message is likely to be BDS code 6,0
Args: Args:
@ -68,7 +66,7 @@ def is60(msg: str) -> bool:
return True return True
def hdg60(msg: str) -> Optional[float]: def hdg60(msg):
"""Megnetic heading of aircraft """Megnetic heading of aircraft
Args: Args:
@ -88,16 +86,16 @@ def hdg60(msg: str) -> Optional[float]:
if sign: if sign:
value = value - 1024 value = value - 1024
hdg = value * 90 / 512 # degree hdg = value * 90 / 512.0 # degree
# convert from [-180, 180] to [0, 360] # convert from [-180, 180] to [0, 360]
if hdg < 0: if hdg < 0:
hdg = 360 + hdg hdg = 360 + hdg
return hdg return round(hdg, 3)
def ias60(msg: str) -> Optional[float]: def ias60(msg):
"""Indicated airspeed """Indicated airspeed
Args: Args:
@ -115,7 +113,7 @@ def ias60(msg: str) -> Optional[float]:
return ias return ias
def mach60(msg: str) -> Optional[float]: def mach60(msg):
"""Aircraft MACH number """Aircraft MACH number
Args: Args:
@ -130,10 +128,10 @@ def mach60(msg: str) -> Optional[float]:
return None return None
mach = common.bin2int(d[24:34]) * 2.048 / 512.0 mach = common.bin2int(d[24:34]) * 2.048 / 512.0
return mach return round(mach, 3)
def vr60baro(msg: str) -> Optional[int]: def vr60baro(msg):
"""Vertical rate from barometric measurement, this value may be very noisy. """Vertical rate from barometric measurement, this value may be very noisy.
Args: Args:
@ -159,7 +157,7 @@ def vr60baro(msg: str) -> Optional[int]:
return roc return roc
def vr60ins(msg: str) -> Optional[int]: def vr60ins(msg):
"""Vertical rate measurd by onbard equiments (IRS, AHRS) """Vertical rate measurd by onbard equiments (IRS, AHRS)
Args: Args:

View File

@ -4,7 +4,7 @@
# Aircraft Airborne status # Aircraft Airborne status
# ------------------------------------------ # ------------------------------------------
from ... import common from pyModeS import common
def is_emergency(msg: str) -> bool: def is_emergency(msg: str) -> bool:
@ -18,9 +18,7 @@ def is_emergency(msg: str) -> bool:
:return: if the aircraft has declared an emergency :return: if the aircraft has declared an emergency
""" """
if common.typecode(msg) != 28: if common.typecode(msg) != 28:
raise RuntimeError( raise RuntimeError("%s: Not an airborne status message, expecting TC=28" % msg)
"%s: Not an airborne status message, expecting TC=28" % msg
)
mb = common.hex2bin(msg)[32:] mb = common.hex2bin(msg)[32:]
subtype = common.bin2int(mb[5:8]) subtype = common.bin2int(mb[5:8])
@ -74,14 +72,12 @@ def emergency_squawk(msg: str) -> str:
:return: aircraft squawk code :return: aircraft squawk code
""" """
if common.typecode(msg) != 28: if common.typecode(msg) != 28:
raise RuntimeError( raise RuntimeError("%s: Not an airborne status message, expecting TC=28" % msg)
"%s: Not an airborne status message, expecting TC=28" % msg
)
msgbin = common.hex2bin(msg) msgbin = common.hex2bin(msg)
# construct the 13 bits Mode A ID code # construct the 13 bits Mode A ID code
idcode = msgbin[43:56] idcode = msgbin[43:49] + "0" + msgbin[49:55]
squawk = common.squawk(idcode) squawk = common.squawk(idcode)
return squawk return squawk

View File

@ -1,552 +0,0 @@
# ------------------------------------------
# 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])

View File

@ -23,66 +23,16 @@ MRAR and MHR
""" """
# ELS - elementary surveillance # ELS - elementary surveillance
from .bds.bds10 import is10, ovc10 from pyModeS.decoder.bds.bds10 import *
from .bds.bds17 import is17, cap17 from pyModeS.decoder.bds.bds17 import *
from .bds.bds20 import is20, cs20 from pyModeS.decoder.bds.bds20 import *
from .bds.bds30 import is30 from pyModeS.decoder.bds.bds30 import *
# ELS - enhanced surveillance # ELS - enhanced surveillance
from .bds.bds40 import ( from pyModeS.decoder.bds.bds40 import *
is40, from pyModeS.decoder.bds.bds50 import *
selalt40fms, from pyModeS.decoder.bds.bds60 import *
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 # MRAR and MHR
from .bds.bds44 import is44, wind44, temp44, p44, hum44, turb44 from pyModeS.decoder.bds.bds44 import *
from .bds.bds45 import is45, turb45, ws45, mb45, ic45, wv45, temp45, p45, rh45 from pyModeS.decoder.bds.bds45 import *
__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",
]

View File

@ -11,56 +11,25 @@ The EHS wrapper imports all functions from the following modules:
import warnings import warnings
from .bds.bds40 import ( from pyModeS.decoder.bds.bds40 import *
is40, from pyModeS.decoder.bds.bds50 import *
selalt40fms, from pyModeS.decoder.bds.bds60 import *
selalt40mcp, from pyModeS.decoder.bds import infer
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.simplefilter("once", DeprecationWarning)
warnings.warn( warnings.warn(
"pms.ehs module is deprecated. Please use pms.commb instead.", "pms.ehs module is deprecated. Please use pms.commb instead.", DeprecationWarning
DeprecationWarning,
) )
def BDS(msg): def BDS(msg):
warnings.warn( warnings.warn(
"pms.ehs.BDS() is deprecated, use pms.bds.infer() instead.", "pms.ehs.BDS() is deprecated, use pms.bds.infer() instead.", DeprecationWarning
DeprecationWarning,
) )
return infer(msg) return infer(msg)
def icao(msg): def icao(msg):
from . import common from pyModeS.decoder.common import icao
return common.icao(msg) return icao(msg)

View File

@ -10,26 +10,14 @@ The ELS wrapper imports all functions from the following modules:
""" """
import warnings 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 import warnings
from .bds.bds17 import cap17, is17
from .bds.bds20 import cs20, is20
from .bds.bds30 import is30
warnings.simplefilter("once", DeprecationWarning) warnings.simplefilter("once", DeprecationWarning)
warnings.warn( warnings.warn(
"pms.els module is deprecated. Please use pms.commb instead.", "pms.els module is deprecated. Please use pms.commb instead.", DeprecationWarning
DeprecationWarning,
) )
__all__ = [
"is10",
"ovc10",
"is17",
"cap17",
"is20",
"cs20",
"is30",
]

View File

@ -1,26 +0,0 @@
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

View File

@ -1,124 +0,0 @@
#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);
}
}

View File

@ -1,13 +0,0 @@
#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

View File

@ -1,4 +0,0 @@
cdef extern from "core.h":
void make_key(int*, long time, long address)
void btea(int*, int, int*)

View File

@ -1,14 +0,0 @@
from typing import Any
from . import DecodedMessage
AIRCRAFT_TYPES: list[str]
def flarm(
timestamp: int,
msg: str,
refLat: float,
refLon: float,
**kwargs: Any,
) -> DecodedMessage: ...

View File

@ -1,147 +0,0 @@
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
)

View File

@ -2,19 +2,13 @@
Decode short roll call surveillance replies, with downlink format 4 or 5 Decode short roll call surveillance replies, with downlink format 4 or 5
""" """
from __future__ import annotations from pyModeS import common
from typing import Callable, TypeVar
from .. import common
T = TypeVar("T")
F = Callable[[str], T]
def _checkdf(func: F[T]) -> F[T]: def _checkdf(func):
"""Ensure downlink format is 4 or 5.""" """Ensure downlink format is 4 or 5."""
def wrapper(msg: str) -> T: def wrapper(msg):
df = common.df(msg) df = common.df(msg)
if df not in [4, 5]: if df not in [4, 5]:
raise RuntimeError( raise RuntimeError(
@ -26,7 +20,7 @@ def _checkdf(func: F[T]) -> F[T]:
@_checkdf @_checkdf
def fs(msg: str) -> tuple[int, str]: def fs(msg):
"""Decode flight status. """Decode flight status.
Args: Args:
@ -37,7 +31,7 @@ def fs(msg: str) -> tuple[int, str]:
""" """
msgbin = common.hex2bin(msg) msgbin = common.hex2bin(msg)
fs = common.bin2int(msgbin[5:8]) fs = common.bin2int(msgbin[5:8])
text = "" text = None
if fs == 0: if fs == 0:
text = "no alert, no SPI, aircraft is airborne" text = "no alert, no SPI, aircraft is airborne"
@ -56,7 +50,7 @@ def fs(msg: str) -> tuple[int, str]:
@_checkdf @_checkdf
def dr(msg: str) -> tuple[int, str]: def dr(msg):
"""Decode downlink request. """Decode downlink request.
Args: Args:
@ -68,7 +62,7 @@ def dr(msg: str) -> tuple[int, str]:
msgbin = common.hex2bin(msg) msgbin = common.hex2bin(msg)
dr = common.bin2int(msgbin[8:13]) dr = common.bin2int(msgbin[8:13])
text = "" text = None
if dr == 0: if dr == 0:
text = "no downlink request" text = "no downlink request"
@ -85,7 +79,7 @@ def dr(msg: str) -> tuple[int, str]:
@_checkdf @_checkdf
def um(msg: str) -> tuple[int, int, None | str]: def um(msg):
"""Decode utility message. """Decode utility message.
Utility message contains interrogator identifier and reservation type. Utility message contains interrogator identifier and reservation type.
@ -111,7 +105,7 @@ def um(msg: str) -> tuple[int, int, None | str]:
@_checkdf @_checkdf
def altitude(msg: str) -> None | int: def altitude(msg):
"""Decode altitude. """Decode altitude.
Args: Args:
@ -125,7 +119,7 @@ def altitude(msg: str) -> None | int:
@_checkdf @_checkdf
def identity(msg: str) -> str: def identity(msg):
"""Decode squawk code. """Decode squawk code.
Args: Args:

View File

@ -1,16 +1,8 @@
"""Uncertainty parameters. """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 NA = None
TC_NUCp_lookup = { TC_NUCp_lookup = {
@ -34,7 +26,7 @@ TC_NUCp_lookup = {
22: 0, 22: 0,
} }
TC_NICv1_lookup: dict[int, int | dict[int, int]] = { TC_NICv1_lookup = {
5: 11, 5: 11,
6: 10, 6: 10,
7: 9, 7: 9,
@ -54,7 +46,7 @@ TC_NICv1_lookup: dict[int, int | dict[int, int]] = {
22: 0, 22: 0,
} }
TC_NICv2_lookup: dict[int, int | dict[int, int]] = { TC_NICv2_lookup = {
5: 11, 5: 11,
6: 10, 6: 10,
7: {2: 9, 0: 8}, 7: {2: 9, 0: 8},
@ -75,13 +67,7 @@ TC_NICv2_lookup: dict[int, int | dict[int, int]] = {
} }
class NUCpEntry(TypedDict): NUCp = {
HPL: None | float
RCu: None | int
RCv: None | int
NUCp: dict[int, NUCpEntry] = {
9: {"HPL": 7.5, "RCu": 3, "RCv": 4}, 9: {"HPL": 7.5, "RCu": 3, "RCv": 4},
8: {"HPL": 25, "RCu": 10, "RCv": 15}, 8: {"HPL": 25, "RCu": 10, "RCv": 15},
7: {"HPL": 185, "RCu": 93, "RCv": NA}, 7: {"HPL": 185, "RCu": 93, "RCv": NA},
@ -94,13 +80,7 @@ NUCp: dict[int, NUCpEntry] = {
0: {"HPL": NA, "RCu": NA, "RCv": NA}, 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}, 0: {"HVE": NA, "VVE": NA},
1: {"HVE": 10, "VVE": 15.2}, 1: {"HVE": 10, "VVE": 15.2},
2: {"HVE": 3, "VVE": 4.5}, 2: {"HVE": 3, "VVE": 4.5},
@ -108,13 +88,7 @@ NUCv: dict[int, NUCvEntry] = {
4: {"HVE": 0.3, "VVE": 0.46}, 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}, 11: {"EPU": 3, "VEPU": 4},
10: {"EPU": 10, "VEPU": 15}, 10: {"EPU": 10, "VEPU": 15},
9: {"EPU": 30, "VEPU": 45}, 9: {"EPU": 30, "VEPU": 45},
@ -129,13 +103,7 @@ NACp: dict[int, NACpEntry] = {
0: {"EPU": NA, "VEPU": NA}, 0: {"EPU": NA, "VEPU": NA},
} }
NACv = {
class NACvEntry(TypedDict):
HFOMr: None | float
VFOMr: None | float
NACv: dict[int, NACvEntry] = {
0: {"HFOMr": NA, "VFOMr": NA}, 0: {"HFOMr": NA, "VFOMr": NA},
1: {"HFOMr": 10, "VFOMr": 15.2}, 1: {"HFOMr": 10, "VFOMr": 15.2},
2: {"HFOMr": 3, "VFOMr": 4.5}, 2: {"HFOMr": 3, "VFOMr": 4.5},
@ -143,13 +111,7 @@ NACv: dict[int, NACvEntry] = {
4: {"HFOMr": 0.3, "VFOMr": 0.46}, 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}, 3: {"PE_RCu": 1e-7, "PE_VPL": 2e-7},
2: {"PE_RCu": 1e-5, "PE_VPL": 1e-5}, 2: {"PE_RCu": 1e-5, "PE_VPL": 1e-5},
1: {"PE_RCu": 1e-3, "PE_VPL": 1e-3}, 1: {"PE_RCu": 1e-3, "PE_VPL": 1e-3},
@ -157,12 +119,7 @@ SIL: dict[int, SILEntry] = {
} }
class NICv1Entry(TypedDict): NICv1 = {
Rc: None | float
VPL: None | float
NICv1: dict[int, dict[int, NICv1Entry]] = {
# NIC is used as the index at second Level # NIC is used as the index at second Level
11: {0: {"Rc": 7.5, "VPL": 11}}, 11: {0: {"Rc": 7.5, "VPL": 11}},
10: {0: {"Rc": 25, "VPL": 37.5}}, 10: {0: {"Rc": 25, "VPL": 37.5}},
@ -178,12 +135,7 @@ NICv1: dict[int, dict[int, NICv1Entry]] = {
0: {0: {"Rc": NA, "VPL": NA}}, 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 # Decimal value of [NICa NICb/NICc] is used as the index at second Level
11: {0: {"Rc": 7.5}}, 11: {0: {"Rc": 7.5}},
10: {0: {"Rc": 25}}, 10: {0: {"Rc": 25}},

View File

@ -1,10 +1,9 @@
from typing import Optional from pyModeS import common
from .. import common
from textwrap import wrap from textwrap import wrap
def uplink_icao(msg: str) -> str: def uplink_icao(msg):
"Calculate the ICAO address from a Mode-S interrogation (uplink message)" """Calculate the ICAO address from a Mode-S interrogation (uplink message)"""
p_gen = 0xFFFA0480 << ((len(msg) - 14) * 4) p_gen = 0xFFFA0480 << ((len(msg) - 14) * 4)
data = int(msg[:-6], 16) data = int(msg[:-6], 16)
PA = int(msg[-6:], 16) PA = int(msg[-6:], 16)
@ -21,20 +20,20 @@ def uplink_icao(msg: str) -> str:
return "%06X" % (ad >> 2) return "%06X" % (ad >> 2)
def uf(msg: str) -> int: def uf(msg):
"""Decode Uplink Format value, bits 1 to 5.""" """Decode Uplink Format value, bits 1 to 5."""
ufbin = common.hex2bin(msg[:2]) ufbin = common.hex2bin(msg[:2])
return min(common.bin2int(ufbin[0:5]), 24) return min(common.bin2int(ufbin[0:5]), 24)
def bds(msg: str) -> Optional[str]: def bds(msg):
"Decode requested BDS register from selective (Roll Call) interrogation." """Decode requested BDS register from selective (Roll Call) interrogation."""
UF = uf(msg) UF = uf(msg)
msgbin = common.hex2bin(msg) msgbin = common.hex2bin(msg)
msgbin_split = wrap(msgbin, 8) msgbin_split = wrap(msgbin, 8)
mbytes = list(map(common.bin2int, msgbin_split)) mbytes = list(map(common.bin2int, msgbin_split))
if UF in {4, 5, 20, 21}: if uf(msg) in {4, 5, 20, 21}:
di = mbytes[1] & 0x7 # DI - Designator Identification di = mbytes[1] & 0x7 # DI - Designator Identification
RR = mbytes[1] >> 3 & 0x1F RR = mbytes[1] >> 3 & 0x1F
@ -44,21 +43,19 @@ def bds(msg: str) -> Optional[str]:
RRS = mbytes[2] & 0x0F RRS = mbytes[2] & 0x0F
BDS2 = RRS BDS2 = RRS
elif di == 3: elif di == 3:
RRS = ((mbytes[2] & 0x1) << 3) | ((mbytes[3] & 0xE0) >> 5) RRS = ((mbytes[2] & 0x1) << 4) | ((mbytes[3] & 0xE0) >> 5)
BDS2 = RRS BDS2 = RRS
else: else:
# for other values of DI, the BDS2 is assumed 0 BDS2 = 0 # for other values of DI, the BDS2 is assumed 0 (as per ICAO Annex 10 Vol IV)
# (as per ICAO Annex 10 Vol IV)
BDS2 = 0
return str(format(BDS1,"X")) + str(format(BDS2,"X")) return str(BDS1) + str(BDS2)
else: else:
return None return None
else: else:
return None return None
def pr(msg: str) -> Optional[int]: def pr(msg):
"""Decode PR (probability of reply) field from All Call interrogation. """Decode PR (probability of reply) field from All Call interrogation.
Interpretation: Interpretation:
0 signifies reply with probability of 1 0 signifies reply with probability of 1
@ -83,7 +80,7 @@ def pr(msg: str) -> Optional[int]:
return None return None
def ic(msg: str) -> Optional[str]: def ic(msg):
"""Decode IC (interrogator code) from a ground-based interrogation.""" """Decode IC (interrogator code) from a ground-based interrogation."""
UF = uf(msg) UF = uf(msg)
@ -91,7 +88,8 @@ def ic(msg: str) -> Optional[str]:
msgbin_split = wrap(msgbin, 8) msgbin_split = wrap(msgbin, 8)
mbytes = list(map(common.bin2int, msgbin_split)) mbytes = list(map(common.bin2int, msgbin_split))
IC = None IC = None
if UF == 11: BDS2 = ""
if uf(msg) == 11:
codeLabel = mbytes[1] & 0x7 codeLabel = mbytes[1] & 0x7
icField = (mbytes[1] >> 3) & 0xF icField = (mbytes[1] >> 3) & 0xF
@ -106,11 +104,11 @@ def ic(msg: str) -> Optional[str]:
} }
IC = ic_switcher.get(codeLabel, "") IC = ic_switcher.get(codeLabel, "")
if UF in {4, 5, 20, 21}: if uf(msg) in {4, 5, 20, 21}:
di = mbytes[1] & 0x7 di = mbytes[1] & 0x7
RR = mbytes[1] >> 3 & 0x1F RR = mbytes[1] >> 3 & 0x1F
if RR > 15: if RR > 15:
BDS1 = RR - 16 # noqa: F841 BDS1 = RR - 16
if di == 0 or di == 1 or di == 7: if di == 0 or di == 1 or di == 7:
# II # II
II = (mbytes[2] >> 4) & 0xF II = (mbytes[2] >> 4) & 0xF
@ -131,7 +129,7 @@ def lockout(msg):
if uf(msg) in {4, 5, 20, 21}: if uf(msg) in {4, 5, 20, 21}:
lockout = False lockout = False
di = mbytes[1] & 0x7 di = mbytes[1] & 0x7
if (di == 1 or di == 7): if di == 7:
# LOS # LOS
if ((mbytes[3] & 0x40) >> 6) == 1: if ((mbytes[3] & 0x40) >> 6) == 1:
lockout = True lockout = True
@ -150,14 +148,18 @@ def uplink_fields(msg):
msgbin_split = wrap(msgbin, 8) msgbin_split = wrap(msgbin, 8)
mbytes = list(map(common.bin2int, msgbin_split)) mbytes = list(map(common.bin2int, msgbin_split))
PR = "" PR = ""
LOS = ""
IC = "" IC = ""
lockout = False lockout = False
di = "" di = ""
RR = "" RR = ""
RRS = "" RRS = ""
BDS = "" BDS1 = ""
BDS2 = ""
if uf(msg) == 11: if uf(msg) == 11:
# Probability of Reply decoding # Probability of Reply decoding
PR = ((mbytes[0] & 0x7) << 1) | ((mbytes[1] & 0x80) >> 7) PR = ((mbytes[0] & 0x7) << 1) | ((mbytes[1] & 0x80) >> 7)
@ -178,8 +180,7 @@ def uplink_fields(msg):
IC = ic_switcher.get(codeLabel, "") IC = ic_switcher.get(codeLabel, "")
if uf(msg) in {4, 5, 20, 21}: if uf(msg) in {4, 5, 20, 21}:
# Decode the DI and get the lockout information conveniently # Decode the DI and get the lockout information conveniently (LSS or LOS)
# (LSS or LOS)
# DI - Designator Identification # DI - Designator Identification
di = mbytes[1] & 0x7 di = mbytes[1] & 0x7
@ -187,16 +188,10 @@ def uplink_fields(msg):
if RR > 15: if RR > 15:
BDS1 = RR - 16 BDS1 = RR - 16
BDS2 = 0 BDS2 = 0
if di == 0: if di == 0 or di == 1:
# II # II
II = (mbytes[2] >> 4) & 0xF II = (mbytes[2] >> 4) & 0xF
IC = "II" + str(II) 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: elif di == 7:
# LOS # LOS
if ((mbytes[3] & 0x40) >> 6) == 1: if ((mbytes[3] & 0x40) >> 6) == 1:
@ -213,11 +208,8 @@ def uplink_fields(msg):
# SI # SI
SI = (mbytes[2] >> 2) & 0x3F SI = (mbytes[2] >> 2) & 0x3F
IC = "SI" + str(SI) IC = "SI" + str(SI)
RRS = ((mbytes[2] & 0x1) << 3) | ((mbytes[3] & 0xE0) >> 5) RRS = ((mbytes[2] & 0x1) << 4) | ((mbytes[3] & 0xE0) >> 5)
BDS2 = RRS BDS2 = RRS
if RR > 15:
BDS = str(format(BDS1,"X")) + str(format(BDS2,"X"))
return { return {
"DI": di, "DI": di,
"IC": IC, "IC": IC,
@ -225,5 +217,5 @@ def uplink_fields(msg):
"PR": PR, "PR": PR,
"RR": RR, "RR": RR,
"RRS": RRS, "RRS": RRS,
"BDS": BDS, "BDS": str(BDS1) + str(BDS2),
} }

View File

@ -17,7 +17,7 @@ Speed conversion at altitude H[m] in ISA
:: ::
Mach = tas2mach(Vtas,H) # true airspeed (Vtas) to mach number conversion Mach = tas2mach(Vtas,H) # true airspeed (Vtas) to mach number conversion
Vtas = mach2tas(Mach,H) # mach number to true airspeed (Vtas) conversion Vtas = mach2tas(Mach,H) # true airspeed (Vtas) to mach number conversion
Vtas = eas2tas(Veas,H) # equivalent airspeed to true airspeed, H in [m] Vtas = eas2tas(Veas,H) # equivalent airspeed to true airspeed, H in [m]
Veas = tas2eas(Vtas,H) # true airspeed to equivent 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] 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 fpm = 0.00508 # ft/min -> m/s
inch = 0.0254 # inch -> m inch = 0.0254 # inch -> m
sqft = 0.09290304 # 1 square foot sqft = 0.09290304 # 1 square foot
nm = 1852 # nautical mile -> m nm = 1852.0 # nautical mile -> m
lbs = 0.453592 # pound -> kg lbs = 0.453592 # pound -> kg
g0 = 9.80665 # m/s2, Sea level gravity constant g0 = 9.80665 # m/s2, Sea level gravity constant
R = 287.05287 # m2/(s2 x K), gas constant, sea level ISA R = 287.05287 # m2/(s2 x K), gas constant, sea level ISA
p0 = 101325 # Pa, air pressure, sea level ISA p0 = 101325.0 # Pa, air pressure, sea level ISA
rho0 = 1.225 # kg/m3, air density, sea level ISA rho0 = 1.225 # kg/m3, air density, sea level ISA
T0 = 288.15 # K, temperature, sea level ISA T0 = 288.15 # K, temperature, sea level ISA
gamma = 1.40 # cp/cv for air gamma = 1.40 # cp/cv for air
gamma1 = 0.2 # (gamma-1)/2 for air gamma1 = 0.2 # (gamma-1)/2 for air
gamma2 = 3.5 # gamma/(gamma-1) for air gamma2 = 3.5 # gamma/(gamma-1) for air
beta = -0.0065 # [K/m] ISA temp gradient below tropopause beta = -0.0065 # [K/m] ISA temp gradient below tropopause
r_earth = 6371000 # m, average earth radius r_earth = 6371000.0 # m, average earth radius
a0 = 340.293988 # m/s, sea level speed of sound ISA, sqrt(gamma*R*T0) 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 # phi = 90 - latitude
phi1 = np.radians(90 - lat1) phi1 = np.radians(90.0 - lat1)
phi2 = np.radians(90 - lat2) phi2 = np.radians(90.0 - lat2)
# theta = longitude # theta = longitude
theta1 = np.radians(lon1) theta1 = np.radians(lon1)
@ -158,16 +158,16 @@ def tas2eas(Vtas, H):
def cas2tas(Vcas, H): def cas2tas(Vcas, H):
"""Calibrated Airspeed to True Airspeed""" """Calibrated Airspeed to True Airspeed"""
p, rho, T = atmos(H) p, rho, T = atmos(H)
qdyn = p0 * ((1 + rho0 * Vcas * Vcas / (7 * p0)) ** 3.5 - 1.0) qdyn = p0 * ((1.0 + rho0 * Vcas * Vcas / (7.0 * p0)) ** 3.5 - 1.0)
Vtas = np.sqrt(7 * p / rho * ((1 + qdyn / p) ** (2 / 7.0) - 1.0)) Vtas = np.sqrt(7.0 * p / rho * ((1.0 + qdyn / p) ** (2.0 / 7.0) - 1.0))
return Vtas return Vtas
def tas2cas(Vtas, H): def tas2cas(Vtas, H):
"""True Airspeed to Calibrated Airspeed""" """True Airspeed to Calibrated Airspeed"""
p, rho, T = atmos(H) p, rho, T = atmos(H)
qdyn = p * ((1 + rho * Vtas * Vtas / (7 * p)) ** 3.5 - 1.0) qdyn = p * ((1.0 + rho * Vtas * Vtas / (7.0 * p)) ** 3.5 - 1.0)
Vcas = np.sqrt(7 * p0 / rho0 * ((qdyn / p0 + 1.0) ** (2 / 7.0) - 1.0)) Vcas = np.sqrt(7.0 * p0 / rho0 * ((qdyn / p0 + 1.0) ** (2.0 / 7.0) - 1.0))
return Vcas return Vcas

View File

@ -1,22 +1,14 @@
from __future__ import annotations
import time import time
import traceback import traceback
import numpy as np import numpy as np
import pyModeS as pms import pyModeS as pms
from typing import Any
import_msg = """
---------------------------------------------------------------------
Warning: pyrtlsdr not installed (required for using RTL-SDR devices)!
---------------------------------------------------------------------"""
try: try:
import rtlsdr # type: ignore import rtlsdr
except ImportError: except:
print(import_msg) print("------------------------------------------------------------------------")
print("! Warining: pyrtlsdr not installed (required for using RTL-SDR devices) ")
print("------------------------------------------------------------------------")
sampling_rate = 2e6 sampling_rate = 2e6
smaples_per_microsec = 2 smaples_per_microsec = 2
@ -32,9 +24,9 @@ th_amp_diff = 0.8 # signal amplitude threshold difference between 0 and 1 bit
class RtlReader(object): class RtlReader(object):
def __init__(self, **kwargs) -> None: def __init__(self, **kwargs):
super(RtlReader, self).__init__() super(RtlReader, self).__init__()
self.signal_buffer: list[float] = [] # amplitude of the sample only self.signal_buffer = [] # amplitude of the sample only
self.sdr = rtlsdr.RtlSdr() self.sdr = rtlsdr.RtlSdr()
self.sdr.sample_rate = sampling_rate self.sdr.sample_rate = sampling_rate
self.sdr.center_freq = modes_frequency self.sdr.center_freq = modes_frequency
@ -47,7 +39,7 @@ class RtlReader(object):
self.exception_queue = None self.exception_queue = None
def _calc_noise(self) -> float: def _calc_noise(self):
"""Calculate noise floor""" """Calculate noise floor"""
window = smaples_per_microsec * 100 window = smaples_per_microsec * 100
total_len = len(self.signal_buffer) total_len = len(self.signal_buffer)
@ -58,7 +50,7 @@ class RtlReader(object):
) )
return min(means) return min(means)
def _process_buffer(self) -> list[list[Any]]: def _process_buffer(self):
"""process raw IQ data in the buffer""" """process raw IQ data in the buffer"""
# update noise floor # update noise floor
@ -78,18 +70,17 @@ class RtlReader(object):
i += 1 i += 1
continue continue
if self._check_preamble(self.signal_buffer[i : i + pbits * 2]):
frame_start = i + pbits * 2 frame_start = i + pbits * 2
if self._check_preamble(self.signal_buffer[i:frame_start]): frame_end = i + pbits * 2 + (fbits + 1) * 2
frame_length = (fbits + 1) * 2 frame_length = (fbits + 1) * 2
frame_end = frame_start + frame_length
frame_pulses = self.signal_buffer[frame_start:frame_end] frame_pulses = self.signal_buffer[frame_start:frame_end]
threshold = max(frame_pulses) * 0.2 threshold = max(frame_pulses) * 0.2
msgbin: list[int] = [] msgbin = []
for j in range(0, frame_length, 2): for j in range(0, frame_length, 2):
j_2 = j + 2 p2 = frame_pulses[j : j + 2]
p2 = frame_pulses[j:j_2]
if len(p2) < 2: if len(p2) < 2:
break break
@ -126,7 +117,7 @@ class RtlReader(object):
return messages return messages
def _check_preamble(self, pulses) -> bool: def _check_preamble(self, pulses):
if len(pulses) != 16: if len(pulses) != 16:
return False return False
@ -136,7 +127,7 @@ class RtlReader(object):
return True return True
def _check_msg(self, msg) -> bool: def _check_msg(self, msg):
df = pms.df(msg) df = pms.df(msg)
msglen = len(msg) msglen = len(msg)
if df == 17 and msglen == 28: if df == 17 and msglen == 28:
@ -146,9 +137,8 @@ class RtlReader(object):
return True return True
elif df in [4, 5, 11] and msglen == 14: elif df in [4, 5, 11] and msglen == 14:
return True return True
return False
def _debug_msg(self, msg) -> None: def _debug_msg(self, msg):
df = pms.df(msg) df = pms.df(msg)
msglen = len(msg) msglen = len(msg)
if df == 17 and msglen == 28: if df == 17 and msglen == 28:
@ -161,7 +151,7 @@ class RtlReader(object):
# print("[*]", msg) # print("[*]", msg)
pass pass
def _read_callback(self, data, rtlsdr_obj) -> None: def _read_callback(self, data, rtlsdr_obj):
amp = np.absolute(data) amp = np.absolute(data)
self.signal_buffer.extend(amp.tolist()) self.signal_buffer.extend(amp.tolist())
@ -169,18 +159,16 @@ class RtlReader(object):
messages = self._process_buffer() messages = self._process_buffer()
self.handle_messages(messages) self.handle_messages(messages)
def handle_messages(self, messages) -> None: def handle_messages(self, messages):
"""re-implement this method to handle the messages""" """re-implement this method to handle the messages"""
for msg, t in messages: for msg, t in messages:
# print("%15.9f %s" % (t, msg)) # print("%15.9f %s" % (t, msg))
pass pass
def stop(self, *args, **kwargs) -> None: def stop(self, *args, **kwargs):
self.sdr.close() self.sdr.close()
def run( def run(self, raw_pipe_in=None, stop_flag=None, exception_queue=None):
self, raw_pipe_in=None, stop_flag=None, exception_queue=None
) -> None:
self.raw_pipe_in = raw_pipe_in self.raw_pipe_in = raw_pipe_in
self.exception_queue = exception_queue self.exception_queue = exception_queue
self.stop_flag = stop_flag self.stop_flag = stop_flag

View File

@ -6,7 +6,6 @@ import time
import pyModeS as pms import pyModeS as pms
import traceback import traceback
import zmq import zmq
import math
class TcpClient(object): class TcpClient(object):
@ -33,7 +32,7 @@ class TcpClient(object):
self.socket.connect("tcp://%s:%s" % (self.host, self.port)) self.socket.connect("tcp://%s:%s" % (self.host, self.port))
def stop(self): def stop(self):
self.socket.close() self.socket.disconnect()
def read_raw_buffer(self): def read_raw_buffer(self):
""" Read raw ADS-B data type. """ Read raw ADS-B data type.
@ -150,103 +149,6 @@ class TcpClient(object):
messages.append([msg, ts]) messages.append([msg, ts])
return messages 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): def read_skysense_buffer(self):
"""Skysense stream format. """Skysense stream format.
@ -390,7 +292,4 @@ if __name__ == "__main__":
port = int(sys.argv[2]) port = int(sys.argv[2])
datatype = sys.argv[3] datatype = sys.argv[3]
client = TcpClient(host=host, port=port, datatype=datatype) client = TcpClient(host=host, port=port, datatype=datatype)
try:
client.run() client.run()
finally:
client.stop()

View File

View File

@ -5,14 +5,14 @@ from textwrap import wrap
def hex2bin(hexstr: str) -> str: def hex2bin(hexstr: str) -> str:
"""Convert a hexadecimal string to binary string, with zero fillings.""" """Convert a hexdecimal string to binary string, with zero fillings."""
num_of_bits = len(hexstr) * 4 num_of_bits = len(hexstr) * 4
binstr = bin(int(hexstr, 16))[2:].zfill(int(num_of_bits)) binstr = bin(int(hexstr, 16))[2:].zfill(int(num_of_bits))
return binstr return binstr
def hex2int(hexstr: str) -> int: def hex2int(hexstr: str) -> int:
"""Convert a hexadecimal string to integer.""" """Convert a hexdecimal string to integer."""
return int(hexstr, 16) return int(hexstr, 16)
@ -22,7 +22,7 @@ def bin2int(binstr: str) -> int:
def bin2hex(binstr: str) -> str: def bin2hex(binstr: str) -> str:
"""Convert a binary string to hexadecimal string.""" """Convert a binary string to hexdecimal string."""
return "{0:X}".format(int(binstr, 2)) return "{0:X}".format(int(binstr, 2))
@ -199,7 +199,7 @@ def cprNL(lat: float) -> int:
nz = 15 nz = 15
a = 1 - np.cos(np.pi / (2 * nz)) a = 1 - np.cos(np.pi / (2 * nz))
b = np.cos(np.pi / 180 * abs(lat)) ** 2 b = np.cos(np.pi / 180.0 * abs(lat)) ** 2
nl = 2 * np.pi / (np.arccos(1 - a / b)) nl = 2 * np.pi / (np.arccos(1 - a / b))
NL = floor(nl) NL = floor(nl)
return NL return NL
@ -231,7 +231,7 @@ def squawk(binstr: str) -> str:
binstr (String): 13 bits binary string binstr (String): 13 bits binary string
Returns: Returns:
string: squawk code int: altitude in ft
""" """
if len(binstr) != 13 or not set(binstr).issubset(set("01")): if len(binstr) != 13 or not set(binstr).issubset(set("01")):
@ -404,85 +404,3 @@ def wrongstatus(data: str, sb: int, msb: int, lsb: int) -> bool:
return True return True
return False 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

View File

@ -1,6 +1,5 @@
import os import os
import time import time
import traceback
import datetime import datetime
import csv import csv
import pyModeS as pms import pyModeS as pms
@ -73,15 +72,8 @@ class Decode:
"VFOMr": None, "VFOMr": None,
"PE_RCu": None, "PE_RCu": None,
"PE_VPL": 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]["t"] = t
self.acs[icao]["live"] = int(t) self.acs[icao]["live"] = int(t)
@ -162,7 +154,7 @@ class Decode:
ac["nic_bc"] = pms.adsb.nic_b(msg) ac["nic_bc"] = pms.adsb.nic_b(msg)
if (5 <= tc <= 8) or (9 <= tc <= 18) or (20 <= tc <= 22): if (5 <= tc <= 8) or (9 <= tc <= 18) or (20 <= tc <= 22):
ac["NUCp"], ac["HPL"], ac["RCu"], ac["RCv"] = pms.adsb.nuc_p(msg) ac["HPL"], ac["RCu"], ac["RCv"] = pms.adsb.nuc_p(msg)
if (ac["ver"] == 1) and ("nic_s" in ac.keys()): if (ac["ver"] == 1) and ("nic_s" in ac.keys()):
ac["Rc"], ac["VPL"] = pms.adsb.nic_v1(msg, ac["nic_s"]) ac["Rc"], ac["VPL"] = pms.adsb.nic_v1(msg, ac["nic_s"])
@ -171,20 +163,20 @@ class Decode:
and ("nic_a" in ac.keys()) and ("nic_a" in ac.keys())
and ("nic_bc" in ac.keys()) and ("nic_bc" in ac.keys())
): ):
ac["NIC"], ac["Rc"] = pms.adsb.nic_v2(msg, ac["nic_a"], ac["nic_bc"]) ac["Rc"] = pms.adsb.nic_v2(msg, ac["nic_a"], ac["nic_bc"])
if tc == 19: if tc == 19:
ac["NUCv"], ac["HVE"], ac["VVE"] = pms.adsb.nuc_v(msg) ac["HVE"], ac["VVE"] = pms.adsb.nuc_v(msg)
if ac["ver"] in [1, 2]: if ac["ver"] in [1, 2]:
ac["NACv"], ac["HFOMr"], ac["VFOMr"] = pms.adsb.nac_v(msg) ac["HFOMr"], ac["VFOMr"] = pms.adsb.nac_v(msg)
if tc == 29: if tc == 29:
ac["PE_RCu"], ac["PE_VPL"], ac["base"] = pms.adsb.sil(msg, ac["ver"]) ac["PE_RCu"], ac["PE_VPL"], ac["base"] = pms.adsb.sil(msg, ac["ver"])
ac["NACp"], ac["HEPU"], ac["VEPU"] = pms.adsb.nac_p(msg) ac["EPU"], ac["VEPU"] = pms.adsb.nac_p(msg)
if tc == 31: if tc == 31:
ac["ver"] = pms.adsb.version(msg) ac["ver"] = pms.adsb.version(msg)
ac["NACp"], ac["HEPU"], ac["VEPU"] = pms.adsb.nac_p(msg) ac["EPU"], ac["VEPU"] = pms.adsb.nac_p(msg)
ac["PE_RCu"], ac["PE_VPL"], ac["sil_base"] = pms.adsb.sil( ac["PE_RCu"], ac["PE_VPL"], ac["sil_base"] = pms.adsb.sil(
msg, ac["ver"] msg, ac["ver"]
) )
@ -201,8 +193,6 @@ class Decode:
if icao not in self.acs: if icao not in self.acs:
continue continue
self.acs[icao]["icao"] = icao
self.acs[icao]["t"] = t
self.acs[icao]["live"] = int(t) self.acs[icao]["live"] = int(t)
bds = pms.bds.infer(msg) bds = pms.bds.infer(msg)
@ -226,10 +216,8 @@ class Decode:
output_buffer.append([t, icao, "rtrk50", rtrk50]) output_buffer.append([t, icao, "rtrk50", rtrk50])
if trk50: if trk50:
self.acs[icao]["trk50"] = trk50
output_buffer.append([t, icao, "trk50", trk50]) output_buffer.append([t, icao, "trk50", trk50])
if gs50: if gs50:
self.acs[icao]["gs50"] = gs50
output_buffer.append([t, icao, "gs50", gs50]) output_buffer.append([t, icao, "gs50", gs50])
elif bds == "BDS60": elif bds == "BDS60":
@ -243,29 +231,16 @@ class Decode:
self.acs[icao]["t60"] = t self.acs[icao]["t60"] = t
if ias60: if ias60:
self.acs[icao]["ias"] = ias60 self.acs[icao]["ias"] = ias60
output_buffer.append([t, icao, "ias60", ias60])
if hdg60: if hdg60:
self.acs[icao]["hdg"] = hdg60 self.acs[icao]["hdg"] = hdg60
output_buffer.append([t, icao, "hdg60", hdg60])
if mach60: if mach60:
self.acs[icao]["mach"] = mach60 self.acs[icao]["mach"] = mach60
output_buffer.append([t, icao, "mach60", mach60])
if roc60baro: if roc60baro:
self.acs[icao]["roc60baro"] = roc60baro
output_buffer.append([t, icao, "roc60baro", roc60baro]) output_buffer.append([t, icao, "roc60baro", roc60baro])
if roc60ins: if roc60ins:
self.acs[icao]["roc60ins"] = roc60ins
output_buffer.append([t, 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 # clear up old data
for icao in list(self.acs.keys()): for icao in list(self.acs.keys()):
if self.t - self.acs[icao]["live"] > self.cache_timeout: if self.t - self.acs[icao]["live"] > self.cache_timeout:

156
pyModeS/streamer/modeslive Executable file
View File

@ -0,0 +1,156 @@
#!/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
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: {}".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 == "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)
# 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()
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)

View File

@ -1,155 +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 # , 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)

View File

@ -1,60 +0,0 @@
[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"

61
setup.py Normal file
View File

@ -0,0 +1,61 @@
"""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
4. twine upload dist/*
"""
# Always prefer setuptools over distutils
from setuptools import setup, find_packages
# To use a consistent encoding
from codecs import open
from os import path
here = path.abspath(path.dirname(__file__))
# Get the long description from the README file
with open(path.join(here, "README.rst"), encoding="utf-8") as f:
long_description = f.read()
details = dict(
name="pyModeS",
version="2.8",
description="Python Mode-S and ADS-B Decoder",
long_description=long_description,
url="https://github.com/junzis/pyModeS",
author="Junzi Sun",
author_email="j.sun-1@tudelft.nl",
license="GNU GPL v3",
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",
],
keywords="Mode-S ADS-B EHS ELS Comm-B",
packages=find_packages(exclude=["contrib", "docs", "tests"]),
install_requires=["numpy", "pyzmq"],
extras_require={"fast": ["Cython"]},
package_data={"pyModeS": ["*.pyx", "*.pxd"]},
scripts=["pyModeS/streamer/modeslive"],
)
try:
from setuptools.extension import Extension
from Cython.Build import cythonize
extensions = [Extension("pyModeS.c_common", ["pyModeS/c_common.pyx"])]
setup(**dict(details, ext_modules=cythonize(extensions)))
except:
setup(**details)

View File

@ -1,7 +1,11 @@
import csv import sys
import time import time
import csv
from pyModeS.decoder import adsb if len(sys.argv) > 1 and sys.argv[1] == "cython":
from pyModeS.c_decoder import adsb
else:
from pyModeS.decoder import adsb
print("===== Decode ADS-B sample data=====") print("===== Decode ADS-B sample data=====")

View File

@ -1,5 +1,4 @@
from pyModeS import adsb from pyModeS import adsb
from pytest import approx
# === TEST ADS-B package === # === TEST ADS-B package ===
@ -23,7 +22,7 @@ def test_adsb_position():
1446332400, 1446332400,
1446332405, 1446332405,
) )
assert pos == (approx(49.81755, 0.001), approx(6.08442, 0.001)) assert pos == (49.81755, 6.08442)
def test_adsb_position_swap_odd_even(): def test_adsb_position_swap_odd_even():
@ -33,32 +32,26 @@ def test_adsb_position_swap_odd_even():
1446332405, 1446332405,
1446332400, 1446332400,
) )
assert pos == (approx(49.81755, 0.001), approx(6.08442, 0.001)) assert pos == (49.81755, 6.08442)
def test_adsb_position_with_ref(): def test_adsb_position_with_ref():
pos = adsb.position_with_ref("8D40058B58C901375147EFD09357", 49.0, 6.0) pos = adsb.position_with_ref("8D40058B58C901375147EFD09357", 49.0, 6.0)
assert pos == (approx(49.82410, 0.001), approx(6.06785, 0.001)) assert pos == (49.82410, 6.06785)
pos = adsb.position_with_ref("8FC8200A3AB8F5F893096B000000", -43.5, 172.5) pos = adsb.position_with_ref("8FC8200A3AB8F5F893096B000000", -43.5, 172.5)
assert pos == (approx(-43.48564, 0.001), approx(172.53942, 0.001)) assert pos == (-43.48564, 172.53942)
def test_adsb_airborne_position_with_ref(): def test_adsb_airborne_position_with_ref():
pos = adsb.airborne_position_with_ref( pos = adsb.airborne_position_with_ref("8D40058B58C901375147EFD09357", 49.0, 6.0)
"8D40058B58C901375147EFD09357", 49.0, 6.0 assert pos == (49.82410, 6.06785)
) pos = adsb.airborne_position_with_ref("8D40058B58C904A87F402D3B8C59", 49.0, 6.0)
assert pos == (approx(49.82410, 0.001), approx(6.06785, 0.001)) assert pos == (49.81755, 6.08442)
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(): def test_adsb_surface_position_with_ref():
pos = adsb.surface_position_with_ref( pos = adsb.surface_position_with_ref("8FC8200A3AB8F5F893096B000000", -43.5, 172.5)
"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_surface_position(): def test_adsb_surface_position():
@ -70,7 +63,7 @@ def test_adsb_surface_position():
-43.496, -43.496,
172.558, 172.558,
) )
assert pos == (approx(-43.48564, 0.001), approx(172.53942, 0.001)) assert pos == (-43.48564, 172.53942)
def test_adsb_alt(): def test_adsb_alt():
@ -81,31 +74,16 @@ def test_adsb_velocity():
vgs = adsb.velocity("8D485020994409940838175B284F") vgs = adsb.velocity("8D485020994409940838175B284F")
vas = adsb.velocity("8DA05F219B06B6AF189400CBC33F") vas = adsb.velocity("8DA05F219B06B6AF189400CBC33F")
vgs_surface = adsb.velocity("8FC8200A3AB8F5F893096B000000") vgs_surface = adsb.velocity("8FC8200A3AB8F5F893096B000000")
assert vgs == (159, approx(182.88, 0.1), -832, "GS") assert vgs == (159, 182.88, -832, "GS")
assert vas == (375, approx(243.98, 0.1), -2304, "TAS") assert vas == (375, 243.98, -2304, "TAS")
assert vgs_surface == (19, approx(42.2, 0.1), 0, "GS") assert vgs_surface == (19, 42.2, 0, "GS")
assert adsb.altitude_diff("8D485020994409940838175B284F") == 550 assert adsb.altitude_diff("8D485020994409940838175B284F") == 550
def test_adsb_emergency(): def test_adsb_emergency():
assert not adsb.is_emergency("8DA2C1B6E112B600000000760759") assert not adsb.is_emergency("8DA2C1B6E112B600000000760759")
assert adsb.emergency_state("8DA2C1B6E112B600000000760759") == 0 assert adsb.emergency_state("8DA2C1B6E112B600000000760759") == 0
assert adsb.emergency_squawk("8DA2C1B6E112B600000000760759") == "6513" assert adsb.emergency_squawk("8DA2C1B6E112B600000000760759") == "6615"
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(): # def test_nic():

View File

@ -6,7 +6,7 @@ def test_icao():
def test_interrogator(): def test_interrogator():
assert allcall.interrogator("5D484FDEA248F5") == "SI6" assert allcall.interrogator("5D484FDEA248F5") == 22
def test_capability(): def test_capability():

View File

@ -1,12 +1,6 @@
import sys
import pytest
from pyModeS import bds 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(): def test_bds_infer():
assert bds.infer("8D406B902015A678D4D220AA4BDA") == "BDS08" assert bds.infer("8D406B902015A678D4D220AA4BDA") == "BDS08"
assert bds.infer("8FC8200A3AB8F5F893096B000000") == "BDS06" assert bds.infer("8FC8200A3AB8F5F893096B000000") == "BDS06"

View File

@ -1,5 +1,4 @@
from pyModeS import bds, commb from pyModeS import bds, commb
from pytest import approx
# from pyModeS import ehs, els # deprecated # from pyModeS import ehs, els # deprecated
@ -23,24 +22,30 @@ def test_bds40_functions():
def test_bds50_functions(): def test_bds50_functions():
msg1 = "A000139381951536E024D4CCF6B5" assert bds.bds50.roll50("A000139381951536E024D4CCF6B5") == 2.1
msg2 = "A0001691FFD263377FFCE02B2BF9" 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
for module in [bds.bds50, commb]: assert commb.roll50("A000139381951536E024D4CCF6B5") == 2.1
assert module.roll50(msg1) == approx(2.1, 0.01) assert commb.roll50("A0001691FFD263377FFCE02B2BF9") == -0.4 # signed value
assert module.roll50(msg2) == approx(-0.35, 0.01) # signed value assert commb.trk50("A000139381951536E024D4CCF6B5") == 114.258
assert module.trk50(msg1) == approx(114.258, 0.1) assert commb.gs50("A000139381951536E024D4CCF6B5") == 438
assert module.gs50(msg1) == 438 assert commb.rtrk50("A000139381951536E024D4CCF6B5") == 0.125
assert module.rtrk50(msg1) == 0.125 assert commb.tas50("A000139381951536E024D4CCF6B5") == 424
assert module.tas50(msg1) == 424
def test_bds60_functions(): def test_bds60_functions():
msg = "A00004128F39F91A7E27C46ADC21" 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
for module in [bds.bds60, commb]: assert commb.hdg60("A00004128F39F91A7E27C46ADC21") == 42.715
assert bds.bds60.hdg60(msg) == approx(42.71484) assert commb.ias60("A00004128F39F91A7E27C46ADC21") == 252
assert bds.bds60.ias60(msg) == 252 assert commb.mach60("A00004128F39F91A7E27C46ADC21") == 0.42
assert bds.bds60.mach60(msg) == 0.42 assert commb.vr60baro("A00004128F39F91A7E27C46ADC21") == -1920
assert bds.bds60.vr60baro(msg) == -1920 assert commb.vr60ins("A00004128F39F91A7E27C46ADC21") == -1920
assert bds.bds60.vr60ins(msg) == -1920