Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff19b54868 | ||
|
|
a30565b4ef | ||
|
|
64923438a1 | ||
|
|
f9225bf375 | ||
|
|
67ced74c0a | ||
|
|
46ffb79234 | ||
|
|
2e9833148b | ||
|
|
c25c9d6b96 | ||
|
|
13f2071bf0 | ||
|
|
3d0c4f0240 | ||
|
|
643f14e725 |
52
.github/workflows/pypi-publish.yml
vendored
52
.github/workflows/pypi-publish.yml
vendored
@@ -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/*
|
||||
|
||||
53
.github/workflows/run-tests.yml
vendored
53
.github/workflows/run-tests.yml
vendored
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
77
build.py
77
build.py
@@ -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
1364
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# BDS 6,1
|
||||
# ADS-B TC=28
|
||||
# Aircraft Airborne status
|
||||
# (Subtype 1)
|
||||
# ------------------------------------------
|
||||
|
||||
from ... import common
|
||||
102
pyModeS/decoder/bds/bds61_st2.py
Normal file
102
pyModeS/decoder/bds/bds61_st2.py
Normal 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)
|
||||
@@ -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
156
pyModeS/streamer/modeslive
Executable 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)
|
||||
@@ -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)
|
||||
@@ -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
3
setup.cfg
Normal file
@@ -0,0 +1,3 @@
|
||||
# https://github.com/embray/setup.cfg
|
||||
[metadata]
|
||||
license_file = LICENSE
|
||||
101
setup.py
Normal file
101
setup.py
Normal 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
40
tests/test_acas.py
Normal 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))
|
||||
Reference in New Issue
Block a user