Compare commits
6
Commits
acas
..
pr_demod2400
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c62b3b48fc | ||
|
|
ae01f95ff5 | ||
|
|
c3839d861c | ||
|
|
a8f3b9c811 | ||
|
|
8f098671a0 | ||
|
|
4cf99e8927 |
@@ -5,13 +5,16 @@ on:
|
|||||||
pull_request_target:
|
pull_request_target:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
POETRY_VERSION: "1.3.1"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
|
python-version: ["3.8", "3.9", "3.10", "3.11"]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
PYTHON_VERSION: ${{ matrix.python-version }}
|
PYTHON_VERSION: ${{ matrix.python-version }}
|
||||||
@@ -24,30 +27,45 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
|
|
||||||
|
# Poetry cache depends on OS, Python version and Poetry version.
|
||||||
|
- name: Cache Poetry cache
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: ~/.cache/pypoetry
|
||||||
|
key: poetry-cache-${{ runner.os }}-${{ env.PYTHON_VERSION }}-${{ env.POETRY_VERSION }}
|
||||||
|
|
||||||
|
# virtualenv cache should depends on OS, Python version and `poetry.lock` (and optionally workflow files).
|
||||||
|
- name: Cache Packages
|
||||||
|
uses: actions/cache@v3
|
||||||
|
if: matrix.os != 'windows-latest'
|
||||||
|
with:
|
||||||
|
path: ~/.local
|
||||||
|
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: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
pip install -U pip numpy cython mypy
|
poetry install
|
||||||
pip install -U pytest codecov pytest-cov
|
|
||||||
pip install .
|
|
||||||
|
|
||||||
- name: Type checking
|
- name: Type checking
|
||||||
if: ${{ env.PYTHON_VERSION != '3.7' }}
|
if: ${{ env.PYTHON_VERSION != '3.7' }}
|
||||||
run: |
|
run: |
|
||||||
mypy pyModeS
|
poetry run mypy pyModeS
|
||||||
|
|
||||||
- name: Run tests (without Cython)
|
- name: Run tests
|
||||||
run: |
|
run: |
|
||||||
pytest tests --cov --cov-report term-missing
|
poetry run 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
|
- name: Upload coverage to Codecov
|
||||||
if: ${{ github.event_name != 'pull_request_target' && env.PYTHON_VERSION == '3.10' }}
|
if: ${{ github.event_name != 'pull_request_target' && env.PYTHON_VERSION == '3.10' }}
|
||||||
|
|||||||
@@ -5,6 +5,11 @@ __pycache__/
|
|||||||
*.py[cod]
|
*.py[cod]
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
|
|
||||||
|
# Cython
|
||||||
|
pyModeS/decoder/flarm/decode.c
|
||||||
|
pyModeS/extra/demod2400/core.c
|
||||||
|
pyModeS/c_common.c
|
||||||
|
|
||||||
# C extensions
|
# C extensions
|
||||||
*.so
|
*.so
|
||||||
|
|
||||||
@@ -64,3 +69,4 @@ target/
|
|||||||
.venv
|
.venv
|
||||||
env/
|
env/
|
||||||
venv/
|
venv/
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -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 (to be compiled on your side)
|
# stable version
|
||||||
pip install pyModeS[fast]
|
pip install pyModeS
|
||||||
|
|
||||||
# development version
|
# development version
|
||||||
git clone https://github.com/junzis/pyModeS
|
git clone https://github.com/junzis/pyModeS
|
||||||
cd pyModeS
|
cd pyModeS
|
||||||
pip install .[fast]
|
poetry install -E rtlsdr
|
||||||
|
|
||||||
|
|
||||||
View live traffic (modeslive)
|
View live traffic (modeslive)
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
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()
|
||||||
Generated
+1364
File diff suppressed because it is too large
Load Diff
@@ -10,7 +10,6 @@ except Exception:
|
|||||||
|
|
||||||
from .decoder import tell
|
from .decoder import tell
|
||||||
from .decoder import adsb
|
from .decoder import adsb
|
||||||
from .decoder import acas
|
|
||||||
from .decoder import commb
|
from .decoder import commb
|
||||||
from .decoder import allcall
|
from .decoder import allcall
|
||||||
from .decoder import surv
|
from .decoder import surv
|
||||||
|
|||||||
+2
-155
@@ -1,158 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
Decoding Air-Air Surveillance (ACAS) DF=0/16
|
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.bds08 import callsign, category
|
||||||
from .bds.bds09 import airborne_velocity, altitude_diff
|
from .bds.bds09 import airborne_velocity, altitude_diff
|
||||||
from .bds.bds61_st1 import emergency_squawk, emergency_state, is_emergency
|
from .bds.bds61 import emergency_squawk, emergency_state, is_emergency
|
||||||
from .bds.bds62 import (
|
from .bds.bds62 import (
|
||||||
altitude_hold_mode,
|
altitude_hold_mode,
|
||||||
approach_mode,
|
approach_mode,
|
||||||
@@ -161,7 +161,9 @@ 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: str, lat_ref: float, lon_ref: float
|
||||||
|
) -> tuple[float, float]:
|
||||||
"""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
|
||||||
|
|||||||
@@ -34,12 +34,14 @@ from . import ( # noqa: F401
|
|||||||
bds45,
|
bds45,
|
||||||
bds50,
|
bds50,
|
||||||
bds60,
|
bds60,
|
||||||
bds61_st1,
|
bds61,
|
||||||
bds62,
|
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.
|
"""Use reference ground speed and trk to determine BDS50 and DBS60.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
# BDS 6,1
|
# BDS 6,1
|
||||||
# ADS-B TC=28
|
# ADS-B TC=28
|
||||||
# Aircraft Airborne status
|
# Aircraft Airborne status
|
||||||
# (Subtype 1)
|
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
|
|
||||||
from ... import common
|
from ... import common
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
# ------------------------------------------
|
|
||||||
# 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,6 +1,7 @@
|
|||||||
from core cimport make_key as c_make_key, btea as c_btea
|
|
||||||
from cpython cimport array
|
from cpython cimport array
|
||||||
|
|
||||||
|
from .core cimport make_key as c_make_key, btea as c_btea
|
||||||
|
|
||||||
import array
|
import array
|
||||||
import math
|
import math
|
||||||
from ctypes import c_byte
|
from ctypes import c_byte
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from .core import demod2400
|
||||||
|
|
||||||
|
__all__ = ["demod2400"]
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import numpy as np
|
||||||
|
import numpy.typing as npt
|
||||||
|
|
||||||
|
def demod2400(data: npt.NDArray[np.uint16], timestamp: float): ...
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
from libc.stdint cimport uint16_t, uint8_t
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from ...c_common cimport crc, df
|
||||||
|
|
||||||
|
cdef extern from "demod2400.h":
|
||||||
|
int demodulate2400(uint16_t *data, uint8_t *msg, int len_data, int* len_msg)
|
||||||
|
|
||||||
|
|
||||||
|
def demod2400(uint16_t[:] data, float timestamp):
|
||||||
|
cdef uint8_t[:] msg_bin
|
||||||
|
cdef int i = 0, j, length, crc_msg = 1
|
||||||
|
cdef long size = data.shape[0]
|
||||||
|
|
||||||
|
msg_bin = np.zeros(14, dtype=np.uint8)
|
||||||
|
|
||||||
|
while i < size:
|
||||||
|
j = demodulate2400(&data[i], &msg_bin[0], size-i, &length)
|
||||||
|
if j == 0:
|
||||||
|
yield dict(
|
||||||
|
# 1 sample data = 2 IQ samples (hence 2*)
|
||||||
|
timestamp=timestamp + 2.*i/2400000.,
|
||||||
|
payload=None,
|
||||||
|
crc=None,
|
||||||
|
index=i,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
i += j
|
||||||
|
msg_clip = np.asarray(msg_bin)[:length]
|
||||||
|
msg = "".join(f"{elt:02X}" for elt in msg_clip)
|
||||||
|
crc_msg = crc(msg)
|
||||||
|
# if df(msg) != 17 or crc_msg == 0:
|
||||||
|
if crc_msg == 0:
|
||||||
|
yield dict(
|
||||||
|
# 1 sample data = 2 IQ samples (hence 2*)
|
||||||
|
timestamp=timestamp + 2.*i/2400000.,
|
||||||
|
payload=msg,
|
||||||
|
crc=crc_msg,
|
||||||
|
index=i,
|
||||||
|
)
|
||||||
|
|
||||||
|
yield dict(
|
||||||
|
# 1 sample data = 2 IQ samples (hence 2*)
|
||||||
|
timestamp=timestamp + 2.*i/2400000.,
|
||||||
|
payload=None,
|
||||||
|
crc=None,
|
||||||
|
index=i,
|
||||||
|
)
|
||||||
|
return
|
||||||
@@ -0,0 +1,256 @@
|
|||||||
|
#include "demod2400.h"
|
||||||
|
|
||||||
|
static inline int slice_phase0(uint16_t *m)
|
||||||
|
{
|
||||||
|
return 5 * m[0] - 3 * m[1] - 2 * m[2];
|
||||||
|
}
|
||||||
|
static inline int slice_phase1(uint16_t *m)
|
||||||
|
{
|
||||||
|
return 4 * m[0] - m[1] - 3 * m[2];
|
||||||
|
}
|
||||||
|
static inline int slice_phase2(uint16_t *m)
|
||||||
|
{
|
||||||
|
return 3 * m[0] + m[1] - 4 * m[2];
|
||||||
|
}
|
||||||
|
static inline int slice_phase3(uint16_t *m)
|
||||||
|
{
|
||||||
|
return 2 * m[0] + 3 * m[1] - 5 * m[2];
|
||||||
|
}
|
||||||
|
static inline int slice_phase4(uint16_t *m)
|
||||||
|
{
|
||||||
|
return m[0] + 5 * m[1] - 5 * m[2] - m[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
int demodulate2400(uint16_t *mag, uint8_t *msg, int len_mag, int *len_msg)
|
||||||
|
{
|
||||||
|
|
||||||
|
uint32_t j;
|
||||||
|
for (j = 0; j < len_mag / 2 - 300; j++)
|
||||||
|
{ // SALE
|
||||||
|
|
||||||
|
uint16_t *preamble = &mag[j];
|
||||||
|
int high;
|
||||||
|
uint32_t base_signal, base_noise;
|
||||||
|
|
||||||
|
// quick check: we must have a rising edge 0->1 and a falling edge 12->13
|
||||||
|
if (!(preamble[0] < preamble[1] && preamble[12] > preamble[13]))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (preamble[1] > preamble[2] && // 1
|
||||||
|
preamble[2] < preamble[3] && preamble[3] > preamble[4] && // 3
|
||||||
|
preamble[8] < preamble[9] && preamble[9] > preamble[10] && // 9
|
||||||
|
preamble[10] < preamble[11])
|
||||||
|
{ // 11-12
|
||||||
|
// peaks at 1,3,9,11-12: phase 3
|
||||||
|
high = (preamble[1] + preamble[3] + preamble[9] + preamble[11] + preamble[12]) / 4;
|
||||||
|
base_signal = preamble[1] + preamble[3] + preamble[9];
|
||||||
|
base_noise = preamble[5] + preamble[6] + preamble[7];
|
||||||
|
}
|
||||||
|
else if (preamble[1] > preamble[2] && // 1
|
||||||
|
preamble[2] < preamble[3] && preamble[3] > preamble[4] && // 3
|
||||||
|
preamble[8] < preamble[9] && preamble[9] > preamble[10] && // 9
|
||||||
|
preamble[11] < preamble[12])
|
||||||
|
{ // 12
|
||||||
|
// peaks at 1,3,9,12: phase 4
|
||||||
|
high = (preamble[1] + preamble[3] + preamble[9] + preamble[12]) / 4;
|
||||||
|
base_signal = preamble[1] + preamble[3] + preamble[9] + preamble[12];
|
||||||
|
base_noise = preamble[5] + preamble[6] + preamble[7] + preamble[8];
|
||||||
|
}
|
||||||
|
else if (preamble[1] > preamble[2] && // 1
|
||||||
|
preamble[2] < preamble[3] && preamble[4] > preamble[5] && // 3-4
|
||||||
|
preamble[8] < preamble[9] && preamble[10] > preamble[11] && // 9-10
|
||||||
|
preamble[11] < preamble[12])
|
||||||
|
{ // 12
|
||||||
|
// peaks at 1,3-4,9-10,12: phase 5
|
||||||
|
high = (preamble[1] + preamble[3] + preamble[4] + preamble[9] + preamble[10] + preamble[12]) / 4;
|
||||||
|
base_signal = preamble[1] + preamble[12];
|
||||||
|
base_noise = preamble[6] + preamble[7];
|
||||||
|
}
|
||||||
|
else if (preamble[1] > preamble[2] && // 1
|
||||||
|
preamble[3] < preamble[4] && preamble[4] > preamble[5] && // 4
|
||||||
|
preamble[9] < preamble[10] && preamble[10] > preamble[11] && // 10
|
||||||
|
preamble[11] < preamble[12])
|
||||||
|
{ // 12
|
||||||
|
// peaks at 1,4,10,12: phase 6
|
||||||
|
high = (preamble[1] + preamble[4] + preamble[10] + preamble[12]) / 4;
|
||||||
|
base_signal = preamble[1] + preamble[4] + preamble[10] + preamble[12];
|
||||||
|
base_noise = preamble[5] + preamble[6] + preamble[7] + preamble[8];
|
||||||
|
}
|
||||||
|
else if (preamble[2] > preamble[3] && // 1-2
|
||||||
|
preamble[3] < preamble[4] && preamble[4] > preamble[5] && // 4
|
||||||
|
preamble[9] < preamble[10] && preamble[10] > preamble[11] && // 10
|
||||||
|
preamble[11] < preamble[12])
|
||||||
|
{ // 12
|
||||||
|
// peaks at 1-2,4,10,12: phase 7
|
||||||
|
high = (preamble[1] + preamble[2] + preamble[4] + preamble[10] + preamble[12]) / 4;
|
||||||
|
base_signal = preamble[4] + preamble[10] + preamble[12];
|
||||||
|
base_noise = preamble[6] + preamble[7] + preamble[8];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// no suitable peaks
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for enough signal
|
||||||
|
if (base_signal * 2 < 3 * base_noise) // about 3.5dB SNR
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Check that the "quiet" bits 6,7,15,16,17 are actually quiet
|
||||||
|
if (preamble[5] >= high ||
|
||||||
|
preamble[6] >= high ||
|
||||||
|
preamble[7] >= high ||
|
||||||
|
preamble[8] >= high ||
|
||||||
|
preamble[14] >= high ||
|
||||||
|
preamble[15] >= high ||
|
||||||
|
preamble[16] >= high ||
|
||||||
|
preamble[17] >= high ||
|
||||||
|
preamble[18] >= high)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// // try all phases
|
||||||
|
// Modes.stats_current.demod_preambles++;
|
||||||
|
// bestmsg = NULL; bestscore = -2; bestphase = -1;
|
||||||
|
for (int try_phase = 4; try_phase <= 8; ++try_phase)
|
||||||
|
{
|
||||||
|
uint16_t *pPtr;
|
||||||
|
int phase, i, bytelen;
|
||||||
|
|
||||||
|
// Decode all the next 112 bits, regardless of the actual message
|
||||||
|
// size. We'll check the actual message type later
|
||||||
|
|
||||||
|
pPtr = &mag[j + 19] + (try_phase / 5);
|
||||||
|
phase = try_phase % 5;
|
||||||
|
|
||||||
|
bytelen = MODES_LONG_MSG_BYTES;
|
||||||
|
for (i = 0; i < bytelen; ++i)
|
||||||
|
{
|
||||||
|
uint8_t theByte = 0;
|
||||||
|
|
||||||
|
switch (phase)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
theByte =
|
||||||
|
(slice_phase0(pPtr) > 0 ? 0x80 : 0) |
|
||||||
|
(slice_phase2(pPtr + 2) > 0 ? 0x40 : 0) |
|
||||||
|
(slice_phase4(pPtr + 4) > 0 ? 0x20 : 0) |
|
||||||
|
(slice_phase1(pPtr + 7) > 0 ? 0x10 : 0) |
|
||||||
|
(slice_phase3(pPtr + 9) > 0 ? 0x08 : 0) |
|
||||||
|
(slice_phase0(pPtr + 12) > 0 ? 0x04 : 0) |
|
||||||
|
(slice_phase2(pPtr + 14) > 0 ? 0x02 : 0) |
|
||||||
|
(slice_phase4(pPtr + 16) > 0 ? 0x01 : 0);
|
||||||
|
|
||||||
|
phase = 1;
|
||||||
|
pPtr += 19;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
theByte =
|
||||||
|
(slice_phase1(pPtr) > 0 ? 0x80 : 0) |
|
||||||
|
(slice_phase3(pPtr + 2) > 0 ? 0x40 : 0) |
|
||||||
|
(slice_phase0(pPtr + 5) > 0 ? 0x20 : 0) |
|
||||||
|
(slice_phase2(pPtr + 7) > 0 ? 0x10 : 0) |
|
||||||
|
(slice_phase4(pPtr + 9) > 0 ? 0x08 : 0) |
|
||||||
|
(slice_phase1(pPtr + 12) > 0 ? 0x04 : 0) |
|
||||||
|
(slice_phase3(pPtr + 14) > 0 ? 0x02 : 0) |
|
||||||
|
(slice_phase0(pPtr + 17) > 0 ? 0x01 : 0);
|
||||||
|
|
||||||
|
phase = 2;
|
||||||
|
pPtr += 19;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
theByte =
|
||||||
|
(slice_phase2(pPtr) > 0 ? 0x80 : 0) |
|
||||||
|
(slice_phase4(pPtr + 2) > 0 ? 0x40 : 0) |
|
||||||
|
(slice_phase1(pPtr + 5) > 0 ? 0x20 : 0) |
|
||||||
|
(slice_phase3(pPtr + 7) > 0 ? 0x10 : 0) |
|
||||||
|
(slice_phase0(pPtr + 10) > 0 ? 0x08 : 0) |
|
||||||
|
(slice_phase2(pPtr + 12) > 0 ? 0x04 : 0) |
|
||||||
|
(slice_phase4(pPtr + 14) > 0 ? 0x02 : 0) |
|
||||||
|
(slice_phase1(pPtr + 17) > 0 ? 0x01 : 0);
|
||||||
|
|
||||||
|
phase = 3;
|
||||||
|
pPtr += 19;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
theByte =
|
||||||
|
(slice_phase3(pPtr) > 0 ? 0x80 : 0) |
|
||||||
|
(slice_phase0(pPtr + 3) > 0 ? 0x40 : 0) |
|
||||||
|
(slice_phase2(pPtr + 5) > 0 ? 0x20 : 0) |
|
||||||
|
(slice_phase4(pPtr + 7) > 0 ? 0x10 : 0) |
|
||||||
|
(slice_phase1(pPtr + 10) > 0 ? 0x08 : 0) |
|
||||||
|
(slice_phase3(pPtr + 12) > 0 ? 0x04 : 0) |
|
||||||
|
(slice_phase0(pPtr + 15) > 0 ? 0x02 : 0) |
|
||||||
|
(slice_phase2(pPtr + 17) > 0 ? 0x01 : 0);
|
||||||
|
|
||||||
|
phase = 4;
|
||||||
|
pPtr += 19;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
theByte =
|
||||||
|
(slice_phase4(pPtr) > 0 ? 0x80 : 0) |
|
||||||
|
(slice_phase1(pPtr + 3) > 0 ? 0x40 : 0) |
|
||||||
|
(slice_phase3(pPtr + 5) > 0 ? 0x20 : 0) |
|
||||||
|
(slice_phase0(pPtr + 8) > 0 ? 0x10 : 0) |
|
||||||
|
(slice_phase2(pPtr + 10) > 0 ? 0x08 : 0) |
|
||||||
|
(slice_phase4(pPtr + 12) > 0 ? 0x04 : 0) |
|
||||||
|
(slice_phase1(pPtr + 15) > 0 ? 0x02 : 0) |
|
||||||
|
(slice_phase3(pPtr + 17) > 0 ? 0x01 : 0);
|
||||||
|
|
||||||
|
phase = 0;
|
||||||
|
pPtr += 20;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
msg[i] = theByte;
|
||||||
|
if (i == 0)
|
||||||
|
{
|
||||||
|
switch (msg[0] >> 3)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
case 4:
|
||||||
|
case 5:
|
||||||
|
case 11:
|
||||||
|
bytelen = MODES_SHORT_MSG_BYTES;
|
||||||
|
*len_msg = MODES_SHORT_MSG_BYTES;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 16:
|
||||||
|
case 17:
|
||||||
|
case 18:
|
||||||
|
case 20:
|
||||||
|
case 21:
|
||||||
|
case 24:
|
||||||
|
*len_msg = MODES_LONG_MSG_BYTES;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
bytelen = 1; // unknown DF, give up immediately
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return j + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Score the mode S message and see if it's any good.
|
||||||
|
// score = scoreModesMessage(msg, i*8);
|
||||||
|
// if (score > bestscore) {
|
||||||
|
// // new high score!
|
||||||
|
// bestmsg = msg;
|
||||||
|
// bestscore = score;
|
||||||
|
// bestphase = try_phase;
|
||||||
|
// // swap to using the other buffer so we don't clobber our demodulated data
|
||||||
|
// // (if we find a better result then we'll swap back, but that's OK because
|
||||||
|
// // we no longer need this copy if we found a better one)
|
||||||
|
// msg = (msg == msg1) ? msg2 : msg1;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#ifndef __DEMOD_2400_H__
|
||||||
|
#define __DEMOD_2400_H__
|
||||||
|
|
||||||
|
#define MODES_LONG_MSG_BYTES 14
|
||||||
|
#define MODES_SHORT_MSG_BYTES 7
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
int demodulate2400(uint16_t *mag, uint8_t *msg, int len_mag, int* len_msg);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
import time
|
||||||
|
import traceback
|
||||||
|
import numpy as np
|
||||||
|
import pyModeS as pms
|
||||||
|
from pyModeS.extra.demod2400 import demod2400
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
import rtlsdr # type: ignore
|
||||||
|
except ImportError:
|
||||||
|
print(
|
||||||
|
"------------------------------------------------------------------------"
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"! Warning: pyrtlsdr not installed (required for using RTL-SDR devices) !"
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"------------------------------------------------------------------------"
|
||||||
|
)
|
||||||
|
|
||||||
|
modes_frequency = 1090e6
|
||||||
|
sampling_rate = 2.4e6
|
||||||
|
buffer_size = 16 * 16384
|
||||||
|
read_size = buffer_size / 2
|
||||||
|
|
||||||
|
|
||||||
|
class RtlReader(object):
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super(RtlReader, self).__init__()
|
||||||
|
self.signal_buffer = [] # amplitude of the sample only
|
||||||
|
self.sdr = rtlsdr.RtlSdr()
|
||||||
|
self.sdr.sample_rate = sampling_rate
|
||||||
|
self.sdr.center_freq = modes_frequency
|
||||||
|
self.sdr.gain = "auto"
|
||||||
|
|
||||||
|
self.debug = kwargs.get("debug", False)
|
||||||
|
self.raw_pipe_in = None
|
||||||
|
self.stop_flag = False
|
||||||
|
self.exception_queue = None
|
||||||
|
|
||||||
|
def _process_buffer(self):
|
||||||
|
"""process raw IQ data in the buffer"""
|
||||||
|
|
||||||
|
# Mode S messages
|
||||||
|
messages = []
|
||||||
|
|
||||||
|
data = (np.array(self.signal_buffer) * 65535).astype(np.uint16)
|
||||||
|
|
||||||
|
for s in demod2400(data, self.timestamp):
|
||||||
|
if s["payload"] is None:
|
||||||
|
idx = s["index"]
|
||||||
|
# reset the buffer
|
||||||
|
self.signal_buffer = self.signal_buffer[idx:]
|
||||||
|
self.timestamp = s["timestamp"]
|
||||||
|
break
|
||||||
|
if self._check_msg(s["payload"]):
|
||||||
|
messages.append([s["payload"], time.time()]) # s["timestamp"]])
|
||||||
|
if self.debug:
|
||||||
|
self._debug_msg(s["payload"])
|
||||||
|
|
||||||
|
self.timestamp = s["timestamp"]
|
||||||
|
return messages
|
||||||
|
|
||||||
|
def _check_msg(self, msg):
|
||||||
|
df = pms.df(msg)
|
||||||
|
msglen = len(msg)
|
||||||
|
if df == 17 and msglen == 28:
|
||||||
|
if pms.crc(msg) == 0:
|
||||||
|
return True
|
||||||
|
elif df in [20, 21] and msglen == 28:
|
||||||
|
return True
|
||||||
|
elif df in [4, 5, 11] and msglen == 14:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _debug_msg(self, msg):
|
||||||
|
df = pms.df(msg)
|
||||||
|
msglen = len(msg)
|
||||||
|
if df == 17 and msglen == 28:
|
||||||
|
print(msg, pms.icao(msg), df, pms.crc(msg))
|
||||||
|
print(pms.tell(msg))
|
||||||
|
elif df in [20, 21] and msglen == 28:
|
||||||
|
print(msg, pms.icao(msg), df)
|
||||||
|
elif df in [4, 5, 11] and msglen == 14:
|
||||||
|
print(msg, pms.icao(msg), df)
|
||||||
|
else:
|
||||||
|
# print("[*]", msg)
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _read_callback(self, data, rtlsdr_obj):
|
||||||
|
amp = np.absolute(data)
|
||||||
|
self.signal_buffer.extend(amp.tolist())
|
||||||
|
|
||||||
|
if len(self.signal_buffer) >= buffer_size:
|
||||||
|
messages = self._process_buffer()
|
||||||
|
self.handle_messages(messages)
|
||||||
|
|
||||||
|
def handle_messages(self, messages):
|
||||||
|
"""re-implement this method to handle the messages"""
|
||||||
|
for msg, t in messages:
|
||||||
|
# print("%15.9f %s" % (t, msg))
|
||||||
|
pass
|
||||||
|
|
||||||
|
def stop(self, *args, **kwargs):
|
||||||
|
self.sdr.close()
|
||||||
|
|
||||||
|
def run(self, raw_pipe_in=None, stop_flag=None, exception_queue=None):
|
||||||
|
self.raw_pipe_in = raw_pipe_in
|
||||||
|
self.exception_queue = exception_queue
|
||||||
|
self.stop_flag = stop_flag
|
||||||
|
|
||||||
|
try:
|
||||||
|
# raise RuntimeError("test exception")
|
||||||
|
self.timestamp = time.time()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
data = self.sdr.read_samples(read_size)
|
||||||
|
self._read_callback(data, None)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
tb = traceback.format_exc()
|
||||||
|
if self.exception_queue is not None:
|
||||||
|
self.exception_queue.put(tb)
|
||||||
|
raise e
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import signal
|
||||||
|
|
||||||
|
rtl = RtlReader()
|
||||||
|
signal.signal(signal.SIGINT, rtl.stop)
|
||||||
|
|
||||||
|
rtl.debug = True
|
||||||
|
rtl.run()
|
||||||
@@ -1,156 +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
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
Executable
+155
@@ -0,0 +1,155 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import argparse
|
||||||
|
import curses
|
||||||
|
import signal
|
||||||
|
import multiprocessing
|
||||||
|
from pyModeS.streamer.decode import Decode
|
||||||
|
from pyModeS.streamer.screen import Screen
|
||||||
|
from pyModeS.streamer.source import NetSource, RtlSdrSource, RtlSdrSource24
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
|
||||||
|
support_rawtypes = ["raw", "beast", "skysense"]
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument(
|
||||||
|
"--source",
|
||||||
|
help='Choose data source, "rtlsdr", "rtlsdr24" or "net"',
|
||||||
|
required=True,
|
||||||
|
default="net",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--connect",
|
||||||
|
help="Define server, port and data type. Supported data types are: {}".format(
|
||||||
|
support_rawtypes
|
||||||
|
),
|
||||||
|
nargs=3,
|
||||||
|
metavar=("SERVER", "PORT", "DATATYPE"),
|
||||||
|
default=None,
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--latlon",
|
||||||
|
help="Receiver latitude and longitude, needed for the surface position, default none",
|
||||||
|
nargs=2,
|
||||||
|
metavar=("LAT", "LON"),
|
||||||
|
default=None,
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--show-uncertainty",
|
||||||
|
dest="uncertainty",
|
||||||
|
help="Display uncertainty values, default off",
|
||||||
|
action="store_true",
|
||||||
|
required=False,
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--dumpto",
|
||||||
|
help="Folder to dump decoded output, default none",
|
||||||
|
required=False,
|
||||||
|
default=None,
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
SOURCE = args.source
|
||||||
|
LATLON = args.latlon
|
||||||
|
UNCERTAINTY = args.uncertainty
|
||||||
|
DUMPTO = args.dumpto
|
||||||
|
|
||||||
|
if SOURCE in ["rtlsdr", "rtlsdr24"]:
|
||||||
|
pass
|
||||||
|
elif SOURCE == "net":
|
||||||
|
if args.connect is None:
|
||||||
|
print("Error: --connect argument must not be empty.")
|
||||||
|
else:
|
||||||
|
SERVER, PORT, DATATYPE = args.connect
|
||||||
|
if DATATYPE not in support_rawtypes:
|
||||||
|
print(
|
||||||
|
"Data type not supported, available ones are %s"
|
||||||
|
% support_rawtypes
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
print('Source must be "rtlsdr" or "net".')
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if DUMPTO is not None:
|
||||||
|
# append to current folder except root is given
|
||||||
|
if DUMPTO[0] != "/":
|
||||||
|
DUMPTO = os.getcwd() + "/" + DUMPTO
|
||||||
|
|
||||||
|
if not os.path.isdir(DUMPTO):
|
||||||
|
print("Error: dump folder (%s) does not exist" % DUMPTO)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# redirect all stdout to null, avoiding messing up with the screen
|
||||||
|
sys.stdout = open(os.devnull, "w")
|
||||||
|
|
||||||
|
raw_pipe_in, raw_pipe_out = multiprocessing.Pipe()
|
||||||
|
ac_pipe_in, ac_pipe_out = multiprocessing.Pipe()
|
||||||
|
exception_queue = multiprocessing.Queue()
|
||||||
|
stop_flag = multiprocessing.Value("b", False)
|
||||||
|
|
||||||
|
if SOURCE == "net":
|
||||||
|
source = NetSource(host=SERVER, port=PORT, rawtype=DATATYPE)
|
||||||
|
elif SOURCE == "rtlsdr":
|
||||||
|
source = RtlSdrSource()
|
||||||
|
elif SOURCE == "rtlsdr24":
|
||||||
|
source = RtlSdrSource24()
|
||||||
|
|
||||||
|
recv_process = multiprocessing.Process(
|
||||||
|
target=source.run, args=(raw_pipe_in, stop_flag, exception_queue)
|
||||||
|
)
|
||||||
|
|
||||||
|
decode = Decode(latlon=LATLON, dumpto=DUMPTO)
|
||||||
|
decode_process = multiprocessing.Process(
|
||||||
|
target=decode.run, args=(raw_pipe_out, ac_pipe_in, exception_queue)
|
||||||
|
)
|
||||||
|
|
||||||
|
screen = Screen(uncertainty=UNCERTAINTY)
|
||||||
|
screen_process = multiprocessing.Process(
|
||||||
|
target=screen.run, args=(ac_pipe_out, exception_queue)
|
||||||
|
)
|
||||||
|
|
||||||
|
def shutdown():
|
||||||
|
stop_flag.value = True
|
||||||
|
curses.endwin()
|
||||||
|
sys.stdout = sys.__stdout__
|
||||||
|
recv_process.terminate()
|
||||||
|
decode_process.terminate()
|
||||||
|
screen_process.terminate()
|
||||||
|
recv_process.join()
|
||||||
|
decode_process.join()
|
||||||
|
screen_process.join()
|
||||||
|
|
||||||
|
def closeall(signal, frame):
|
||||||
|
print("KeyboardInterrupt (ID: {}). Cleaning up...".format(signal))
|
||||||
|
shutdown()
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
signal.signal(signal.SIGINT, closeall)
|
||||||
|
|
||||||
|
recv_process.start()
|
||||||
|
decode_process.start()
|
||||||
|
screen_process.start()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
if (
|
||||||
|
(not recv_process.is_alive())
|
||||||
|
or (not decode_process.is_alive())
|
||||||
|
or (not screen_process.is_alive())
|
||||||
|
):
|
||||||
|
shutdown()
|
||||||
|
while not exception_queue.empty():
|
||||||
|
trackback = exception_queue.get()
|
||||||
|
print(trackback)
|
||||||
|
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
time.sleep(0.01)
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import pyModeS as pms
|
import pyModeS as pms
|
||||||
from pyModeS.extra.tcpclient import TcpClient
|
from pyModeS.extra.tcpclient import TcpClient
|
||||||
from pyModeS.extra.rtlreader import RtlReader
|
from pyModeS.extra.rtlreader import RtlReader
|
||||||
|
from pyModeS.extra.demod2400.rtlreader import RtlReader as RtlReader24
|
||||||
|
|
||||||
|
|
||||||
class NetSource(TcpClient):
|
class NetSource(TcpClient):
|
||||||
@@ -89,3 +90,47 @@ class RtlSdrSource(RtlReader):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.reset_local_buffer()
|
self.reset_local_buffer()
|
||||||
|
|
||||||
|
|
||||||
|
class RtlSdrSource24(RtlReader24):
|
||||||
|
def __init__(self):
|
||||||
|
super(RtlSdrSource24, self).__init__()
|
||||||
|
self.reset_local_buffer()
|
||||||
|
|
||||||
|
def reset_local_buffer(self):
|
||||||
|
self.local_buffer_adsb_msg = []
|
||||||
|
self.local_buffer_adsb_ts = []
|
||||||
|
self.local_buffer_commb_msg = []
|
||||||
|
self.local_buffer_commb_ts = []
|
||||||
|
|
||||||
|
def handle_messages(self, messages):
|
||||||
|
|
||||||
|
if self.stop_flag.value is True:
|
||||||
|
self.stop()
|
||||||
|
return
|
||||||
|
|
||||||
|
for msg, t in messages:
|
||||||
|
if len(msg) < 28: # only process long messages
|
||||||
|
continue
|
||||||
|
|
||||||
|
df = pms.df(msg)
|
||||||
|
|
||||||
|
if df == 17 or df == 18:
|
||||||
|
self.local_buffer_adsb_msg.append(msg)
|
||||||
|
self.local_buffer_adsb_ts.append(t)
|
||||||
|
elif df == 20 or df == 21:
|
||||||
|
self.local_buffer_commb_msg.append(msg)
|
||||||
|
self.local_buffer_commb_ts.append(t)
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if len(self.local_buffer_adsb_msg) > 1:
|
||||||
|
self.raw_pipe_in.send(
|
||||||
|
{
|
||||||
|
"adsb_ts": self.local_buffer_adsb_ts,
|
||||||
|
"adsb_msg": self.local_buffer_adsb_msg,
|
||||||
|
"commb_ts": self.local_buffer_commb_ts,
|
||||||
|
"commb_msg": self.local_buffer_commb_msg,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.reset_local_buffer()
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
[tool.poetry]
|
||||||
|
name = "pyModeS"
|
||||||
|
version = "2.11"
|
||||||
|
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"
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# https://github.com/embray/setup.cfg
|
|
||||||
[metadata]
|
|
||||||
license_file = LICENSE
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
"""A setuptools based setup module.
|
|
||||||
|
|
||||||
See:
|
|
||||||
https://packaging.python.org/en/latest/distributing.html
|
|
||||||
https://github.com/pypa/sampleproject
|
|
||||||
|
|
||||||
Steps for deploying a new version:
|
|
||||||
1. Increase the version number
|
|
||||||
2. remove the old deployment under [dist] and [build] folder
|
|
||||||
3. run: python setup.py sdist
|
|
||||||
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)
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
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