11 Commits
v2.12 ... acas

Author SHA1 Message Date
Xavier Olive
ff19b54868 fix typing 2022-12-29 19:22:35 +01:00
Xavier Olive
a30565b4ef restore missing file 2022-12-29 19:15:39 +01:00
Junzi Sun
64923438a1 Merge branch 'master' into acas 2022-12-26 18:33:11 +01:00
Junzi Sun
f9225bf375 merge main 2022-12-26 18:27:23 +01:00
Junzi Sun
67ced74c0a fix spaces 2022-12-26 18:22:24 +01:00
Junzi Sun
46ffb79234 add VDS check in ACAS decoding 2020-12-02 22:29:42 +01:00
Junzi Sun
2e9833148b update ACAS and BDS61 structures and functions 2020-05-23 23:40:49 +02:00
Junzi Sun
c25c9d6b96 Merge branch 'master' into pr-71/tuftedocelot/acas 2020-05-23 21:53:51 +02:00
tuftedocelot
13f2071bf0 Add indicators for do not pass
What to do with ARA though? Really need actual ACAS messages for
reference versus reading the spec only
2020-05-20 20:44:47 -05:00
tuftedocelot
3d0c4f0240 Add previously existing work 2020-05-20 19:56:07 -05:00
tuftedocelot
643f14e725 beginnings of acas 2020-05-20 19:51:58 -05:00
18 changed files with 595 additions and 1754 deletions

View File

@@ -1,63 +1,29 @@
# This workflows will upload a Python Package using Twine when a release is created
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
name: publish
name: PyPI Publish
on:
release:
types: [created]
workflow_dispatch:
env:
POETRY_VERSION: "1.3.1"
PYTHON_VERSION: "3.10"
jobs:
deploy:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.8", "3.9", "3.10", "3.11"]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v2
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install and configure Poetry
uses: snok/install-poetry@v1.3.3
with:
version: ${{ env.POETRY_VERSION }}
virtualenvs-create: true
virtualenvs-in-project: true
- name: Display Python version
run: poetry run python -c "import sys; print(sys.version)"
- name: Build packages
run: poetry build
python-version: "3.x"
- name: Install dependencies
run: |
poetry run pip install --upgrade pip
poetry run pip install twine
python -m pip install --upgrade pip
pip install setuptools wheel twine
- name: Build and publish
env:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
run: |
poetry run twine upload dist/*.whl
- name: Build and publish (source)
if: "startsWith(runner.os, 'ubuntu') and env.PYTHON_VERSION == '3.10'"
env:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
run: |
poetry run twine upload dist/*.tar.gz
python setup.py sdist bdist_wheel
twine upload dist/*

View File

@@ -5,16 +5,13 @@ on:
pull_request_target:
workflow_dispatch:
env:
POETRY_VERSION: "1.3.1"
jobs:
deploy:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.8", "3.9", "3.10", "3.11"]
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
env:
PYTHON_VERSION: ${{ matrix.python-version }}
@@ -27,45 +24,33 @@ jobs:
with:
python-version: ${{ matrix.python-version }}
# virtualenv cache should depends on OS, Python version and `poetry.lock` (and optionally workflow files).
- name: Cache Packages
uses: actions/cache@v3
if: runner.os != 'windows-latest'
with:
path: |
~/.local
.venv
key: poetry-${{ runner.os }}-${{ env.PYTHON_VERSION }}-${{ hashFiles('**/poetry.lock') }}
- name: Add poetry to windows path
if: "startsWith(runner.os, 'windows')"
run: |
echo "C:\Users\runneradmin\.local\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
- name: Install and configure Poetry
uses: snok/install-poetry@v1.3.3
with:
version: ${{ env.POETRY_VERSION }}
virtualenvs-create: true
virtualenvs-in-project: true
- name: Display Python version
run: poetry run python -c "import sys; print(sys.version)"
- name: Install dependencies
run: |
poetry install
pip install -U pip numpy cython mypy
pip install -U pytest codecov pytest-cov
pip install .
- name: Type checking
if: ${{ env.PYTHON_VERSION != '3.7' }}
run: |
poetry run mypy pyModeS
mypy pyModeS
- name: Run tests
- name: Run tests (without Cython)
run: |
poetry run pytest tests --cov --cov-report term-missing
pytest tests --cov --cov-report term-missing
- name: Install with Cython
run: |
pip install -U cython
pip uninstall -y pymodes
pip install .
- name: Run tests (with Cython)
run: |
pytest tests
- name: Upload coverage to Codecov
if: ${{ github.event_name != 'pull_request_target' }}
if: ${{ github.event_name != 'pull_request_target' && env.PYTHON_VERSION == '3.10' }}
uses: codecov/codecov-action@v2
with:
env_vars: PYTHON_VERSION

View File

@@ -94,13 +94,13 @@ If you want to make use of the (faster) c module, install ``pyModeS`` as follows
# conda (compiled) version
conda install -c conda-forge pymodes
# stable version
pip install pyModeS
# stable version (to be compiled on your side)
pip install pyModeS[fast]
# development version
git clone https://github.com/junzis/pyModeS
cd pyModeS
poetry install -E rtlsdr
pip install .[fast]
View live traffic (modeslive)

View File

@@ -1,77 +0,0 @@
import os
import shutil
import sys
from distutils.core import Distribution, Extension
from distutils.command import build_ext
from Cython.Build import cythonize
def build() -> None:
compile_args = []
if sys.platform == "linux":
compile_args += [
"-march=native",
"-O3",
"-msse",
"-msse2",
"-mfma",
"-mfpmath=sse",
"-Wno-pointer-sign",
"-Wno-unused-variable",
]
extensions = [
Extension(
"pyModeS.c_common",
["pyModeS/c_common.pyx"],
extra_compile_args=compile_args,
),
Extension(
"pyModeS.decoder.flarm.decode",
[
"pyModeS/decoder/flarm/decode.pyx",
"pyModeS/decoder/flarm/core.c",
],
extra_compile_args=compile_args,
include_dirs=["pyModeS/decoder/flarm"],
),
# Extension(
# "pyModeS.extra.demod2400.core",
# [
# "pyModeS/extra/demod2400/core.pyx",
# "pyModeS/extra/demod2400/demod2400.c",
# ],
# extra_compile_args=compile_args,
# include_dirs=["pyModeS/extra/demod2400"],
# libraries=["m"],
# ),
]
ext_modules = cythonize(
extensions,
compiler_directives={"binding": True, "language_level": 3},
)
distribution = Distribution(
{"name": "extended", "ext_modules": ext_modules}
)
distribution.package_dir = "extended" # type: ignore
cmd = build_ext.build_ext(distribution)
cmd.verbose = True # type: ignore
cmd.ensure_finalized() # type: ignore
cmd.run()
# Copy built extensions back to the project
for output in cmd.get_outputs():
relative_extension = os.path.relpath(output, cmd.build_lib)
shutil.copyfile(output, relative_extension)
mode = os.stat(relative_extension).st_mode
mode |= (mode & 0o444) >> 2
os.chmod(relative_extension, mode)
if __name__ == "__main__":
build()

1364
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,6 +10,7 @@ except Exception:
from .decoder import tell
from .decoder import adsb
from .decoder import acas
from .decoder import commb
from .decoder import allcall
from .decoder import surv

View File

@@ -1,5 +1,158 @@
"""
Decoding Air-Air Surveillance (ACAS) DF=0/16
[To be implemented]
"""
from __future__ import annotations
from .. import common
import warnings
warnings.simplefilter("always", UserWarning)
def isACAS(msg: str) -> bool:
"""Check if the message is an ACAS coordination message.
:param msg: 28 hexdigits string
:return: if VDS is 3,1
"""
mv = common.hex2bin(common.data(msg))
vds = mv[0:8]
if vds == "00110000":
return True
else:
return False
def rac(msg: str) -> None | str:
"""Resolution Advisory Complement.
:param msg: 28 hexdigits string
:return: RACs
"""
if not isACAS(msg):
warnings.warn("Not an ACAS coordination message.")
return None
RAC = []
mv = common.hex2bin(common.data(msg))
if mv[22] == "1":
RAC.append("do not pass below")
if mv[23] == "1":
RAC.append("do not pass above")
if mv[24] == "1":
RAC.append("do not pass left")
if mv[25] == "1":
RAC.append("do not pass right")
return "; ".join(RAC)
def rat(msg: str) -> None | int:
"""RA terminated indicator
Mode S transponder is still required to report RA 18 seconds after
it is terminated by ACAS. Hence, the RAT filed is used.
:param msg: 28 hexdigits string
:return: if RA has been terminated
"""
if not isACAS(msg):
warnings.warn("Not an ACAS coordination message.")
return None
mv = common.hex2bin(common.data(msg))
mte = int(mv[26])
return mte
def mte(msg: str) -> None | int:
"""Multiple threat encounter.
:param msg: 28 hexdigits string
:return: if there are multiple threats
"""
if not isACAS(msg):
warnings.warn("Not an ACAS coordination message.")
return None
mv = common.hex2bin(common.data(msg))
mte = int(mv[27])
return mte
def ara(msg: str) -> None | str:
"""Decode active resolution advisory.
:param msg: 28 bytes hexadecimal message string
:return: RA charactristics
"""
if not isACAS(msg):
warnings.warn("Not an ACAS coordination message.")
return None
mv = common.hex2bin(common.data(msg))
mte = int(mv[27])
ara_b1 = int(mv[8])
ara_b2 = int(mv[9])
ara_b3 = int(mv[10])
ara_b4 = int(mv[11])
ara_b5 = int(mv[12])
ara_b6 = int(mv[14])
ara_b7 = int(mv[15])
# ACAS III are bits 15-22
RA = []
if ara_b1 == 1:
if ara_b2:
RA.append("corrective")
else:
RA.append("preventive")
if ara_b3:
RA.append("downward sense")
else:
RA.append("upward sense")
if ara_b4:
RA.append("increased rate")
if ara_b5:
RA.append("sense reversal")
if ara_b6:
RA.append("altitude crossing")
if ara_b7:
RA.append("positive")
else:
RA.append("vertical speed limit")
if ara_b1 == 0 and mte == 1:
if ara_b2:
RA.append("requires a correction in the upward sense")
if ara_b3:
RA.append("requires a positive climb")
if ara_b4:
RA.append("requires a correction in downward sense")
if ara_b5:
RA.append("requires a positive descent")
if ara_b6:
RA.append("requires a crossing")
if ara_b7:
RA.append("requires a sense reversal")
return "; ".join(RA)

View File

@@ -25,7 +25,7 @@ from .bds.bds06 import (
)
from .bds.bds08 import callsign, category
from .bds.bds09 import airborne_velocity, altitude_diff
from .bds.bds61 import emergency_squawk, emergency_state, is_emergency
from .bds.bds61_st1 import emergency_squawk, emergency_state, is_emergency
from .bds.bds62 import (
altitude_hold_mode,
approach_mode,
@@ -161,9 +161,7 @@ def position(
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: str, lat_ref: float, lon_ref: float) -> tuple[float, float]:
"""Decode position with only one message.
A reference position is required, which can be previously

View File

@@ -34,14 +34,12 @@ from . import ( # noqa: F401
bds45,
bds50,
bds60,
bds61,
bds61_st1,
bds62,
)
def is50or60(
msg: str, spd_ref: float, trk_ref: float, alt_ref: float
) -> Optional[str]:
def is50or60(msg: str, spd_ref: float, trk_ref: float, alt_ref: float) -> Optional[str]:
"""Use reference ground speed and trk to determine BDS50 and DBS60.
Args:

View File

@@ -2,6 +2,7 @@
# BDS 6,1
# ADS-B TC=28
# Aircraft Airborne status
# (Subtype 1)
# ------------------------------------------
from ... import common

View File

@@ -0,0 +1,102 @@
# ------------------------------------------
# BDS 6,1
# ADS-B TC=28
# Aircraft Airborne status
# (Subtype 1)
# ------------------------------------------
from __future__ import annotations
from ... import common
from .. import acas
def threat_type(msg: str) -> int:
"""Determine the threat type indicator.
Value Meaning
----- ---------------------------------------
0 No identity data in TID
1 TID has Mode S address (ICAO)
2 TID has altitude, range, and bearing
3 Not assigned
:param msg: 28 hexdigits string
:return: indicator of threat type
"""
mb = common.hex2bin(common.data(msg))
tti = common.bin2int(mb[28:30])
return tti
def threat_identity(msg: str) -> str:
mb = common.hex2bin(common.data(msg))
tti = threat_type(msg)
# The ICAO of the threat is announced
if tti == 1:
return common.icao(mb[30:55])
else:
raise RuntimeError("%s: Missing threat identity (ICAO)")
def threat_location(msg: str) -> None | tuple[int, int, int]:
"""Get the altitude, range, and bearing of the threat.
Altitude is the Mode C altitude
:param msg: 28 hexdigits string
:return: tuple of the Mode C altitude, range, and bearing
"""
mb = common.hex2bin(common.data(msg))
tti = threat_type(msg)
# Altitude, range, and bearing of threat
if tti == 2:
altitude = common.altitude(mb[31:44])
distance = common.bin2int(mb[44:51])
bearing = common.bin2int(mb[51:57])
return altitude, distance, bearing
return None
def has_multiple_threats(msg: str) -> bool:
"""Indicate if the ACAS is processing multiple threats simultaneously.
:param msg: 28 hexdigits string
:return: if there are multiple threats
"""
return acas.mte(msg) == 1
def active_resolution_advisories(msg: str) -> None | str:
"""Decode active resolution advisory.
Uses ARA decoding function from ACAS module.
:param msg: 28 bytes hexadecimal message string
:return: RA charactristics
"""
return acas.ara(msg)
def is_ra_terminated(msg: str) -> bool:
"""Indicate if the threat is still being generated.
Mode S transponder is still required to report RA 18 seconds after
it is terminated by ACAS. Hence, the RAT filed is used.
:param msg: 28 hexdigits string
:return: if the threat is terminated
"""
return acas.rat(msg) == 1
def ra_complement(msg: str) -> None | str:
"""Resolution Advisory Complement.
:param msg: 28 hexdigits string
:return: RACs
"""
return acas.rac(msg)

View File

@@ -1,7 +1,6 @@
from core cimport make_key as c_make_key, btea as c_btea
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

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,66 +0,0 @@
[tool.poetry]
name = "pyModeS"
version = "2.12"
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 = "src/pyModeS/**/*.so", format = "wheel" }
]
[tool.poetry.build]
generate-setup-file = false
script = "build.py"
[tool.poetry.scripts]
modeslive = "pyModeS.streamer.modeslive:main"
[tool.poetry.dependencies]
python = "^3.8"
numpy = "^1.24"
pyzmq = "^24.0"
pyrtlsdr = {version = "^0.2.93", optional = true}
[tool.poetry.group.dev.dependencies]
Cython = "^0.29.32"
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"
ipykernel = "^6.20.0"
[tool.poetry.extras]
rtlsdr = ["pyrtlsdr"]
[tool.black]
line-length = 80
target_version = ['py38', 'py39', 'py310', 'py311']
include = '\.pyi?$'
[tool.isort]
line_length = 80
profile = "black"
[build-system]
requires = ["poetry-core>=1.0.0", "Cython>=0.29.32"]
build-backend = "poetry.core.masonry.api"

3
setup.cfg Normal file
View File

@@ -0,0 +1,3 @@
# https://github.com/embray/setup.cfg
[metadata]
license_file = LICENSE

101
setup.py Normal file
View File

@@ -0,0 +1,101 @@
"""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/*
"""
import sys
# 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.11",
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"]),
# typing_extensions are no longer necessary after Python 3.8 (TypedDict)
install_requires=["numpy", "pyzmq", "typing_extensions"],
extras_require={"fast": ["Cython"]},
package_data={
"pyModeS": ["*.pyx", "*.pxd", "py.typed"],
"pyModeS.decoder.flarm": ["*.pyx", "*.pxd", "*.pyi"],
},
scripts=["pyModeS/streamer/modeslive"],
)
try:
from distutils.core import Extension
from Cython.Build import cythonize
compile_args = []
include_dirs = ["pyModeS/decoder/flarm"]
if sys.platform == "linux":
compile_args += [
"-march=native",
"-O3",
"-msse",
"-msse2",
"-mfma",
"-mfpmath=sse",
"-Wno-pointer-sign",
]
extensions = [
Extension("pyModeS.c_common", ["pyModeS/c_common.pyx"]),
Extension(
"pyModeS.decoder.flarm.decode",
[
"pyModeS/decoder/flarm/decode.pyx",
"pyModeS/decoder/flarm/core.c",
],
extra_compile_args=compile_args,
include_dirs=include_dirs,
),
]
setup(
**dict(
details,
ext_modules=cythonize(
extensions,
include_path=include_dirs,
compiler_directives={"binding": True, "language_level": 3},
),
)
)
except ImportError:
setup(**details)

40
tests/test_acas.py Normal file
View File

@@ -0,0 +1,40 @@
import pyModeS as pms
msgs = [
"80E1983958C392E8710C642D0BAD",
"8609F8E19E90653083FDE096BE26",
"80011B41F40017D0000012E06B45",
"808182365813667446EB715E253D",
"80E1953058AB029BE8F4E60D989E",
"8667ECFA8FE06B30E59A124D5AEF",
"80030AA000042C0AD05D6205DB2C",
"8631A80000373C463B6E7A00008B",
"80E19131588B146108DE703F3C47",
"825CC4A0001595C4600030A40000",
"808182365813667438EB710EB6D8",
"80A18393581D3655E90354A664B2",
"8081823658136309B4F22BCB5F8D",
"80E1953058AB06516E8602756DD8",
"80E1991058C9063AB6A3E4744DC3",
"8073EC91840AFCED8F300BE765C5",
"8571F54AA814BF8130066A19A31D",
"864070E1990D3B1E78048BAE6987",
"80E1991058C9063AB6A3E4744DC3",
"80818298581581F7CAF8C3C1EEA4",
"80004E98BC90000FF01010951298",
"8526D57E4D963C92CDEE1B6C7C49",
"802A613C93F65A7FF803A51B5ADB",
"85B6C54279B67BE2A0001998FFDA",
"851944F15881648338D1AF4B7A27",
"8321014858208000787905B0E800",
"866DD078EDEBD330404FFFAE9BA5",
"80E196905AB503260E835D849E35",
]
for msg in msgs:
print("-" * 80)
print(msg)
print("ARA", pms.acas.ara(msg))
print("RAC", pms.acas.rac(msg))
print("MTE", pms.acas.mte(msg))
print("RAT", pms.acas.rat(msg))