Compare commits
3 Commits
quantities
...
pr_demod24
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c62b3b48fc | ||
|
|
ae01f95ff5 | ||
|
|
c3839d861c |
3
.flake8
Normal file
3
.flake8
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[flake8]
|
||||||
|
max-line-length = 80
|
||||||
|
extend-ignore = E203, E302
|
||||||
11
.github/dependabot.yml
vendored
11
.github/dependabot.yml
vendored
@@ -1,11 +0,0 @@
|
|||||||
version: 2
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: "pip"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: "monthly"
|
|
||||||
|
|
||||||
- package-ecosystem: "github-actions"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: "monthly"
|
|
||||||
56
.github/workflows/pypi-publish.yml
vendored
56
.github/workflows/pypi-publish.yml
vendored
@@ -1,67 +1,29 @@
|
|||||||
# This workflows will upload a Python Package using Twine when a release is created
|
# 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
|
# 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:
|
on:
|
||||||
release:
|
release:
|
||||||
types: [created]
|
types: [created]
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
env:
|
|
||||||
POETRY_VERSION: "1.3.1"
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
os: [windows-latest, ubuntu-latest, macos-latest]
|
|
||||||
python-version: ["3.8", "3.9", "3.10", "3.11"]
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: "3.x"
|
||||||
|
|
||||||
- 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: Build packages
|
|
||||||
run: poetry build
|
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
poetry run pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
poetry run pip install twine
|
pip install setuptools wheel twine
|
||||||
|
|
||||||
- name: Build and publish
|
- name: Build and publish
|
||||||
env:
|
env:
|
||||||
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
|
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
|
||||||
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
|
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
|
||||||
run: |
|
run: |
|
||||||
poetry run twine upload dist/*.whl
|
python setup.py sdist bdist_wheel
|
||||||
|
twine upload dist/*
|
||||||
- name: Build and publish (source)
|
|
||||||
if: ${{ startsWith(runner.os, 'windows') && matrix.python-version == '3.11' }}
|
|
||||||
env:
|
|
||||||
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
|
|
||||||
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
|
|
||||||
run: |
|
|
||||||
poetry run twine upload dist/*.tar.gz
|
|
||||||
|
|||||||
24
.github/workflows/run-tests.yml
vendored
24
.github/workflows/run-tests.yml
vendored
@@ -20,21 +20,26 @@ jobs:
|
|||||||
PYTHON_VERSION: ${{ matrix.python-version }}
|
PYTHON_VERSION: ${{ matrix.python-version }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v2
|
||||||
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).
|
# virtualenv cache should depends on OS, Python version and `poetry.lock` (and optionally workflow files).
|
||||||
- name: Cache Packages
|
- name: Cache Packages
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
if: ${{ !startsWith(runner.os, 'windows') }}
|
if: matrix.os != 'windows-latest'
|
||||||
with:
|
with:
|
||||||
path: |
|
path: ~/.local
|
||||||
~/.local
|
|
||||||
.venv
|
|
||||||
key: poetry-${{ runner.os }}-${{ env.PYTHON_VERSION }}-${{ hashFiles('**/poetry.lock') }}
|
key: poetry-${{ runner.os }}-${{ env.PYTHON_VERSION }}-${{ hashFiles('**/poetry.lock') }}
|
||||||
|
|
||||||
- name: Add poetry to windows path
|
- name: Add poetry to windows path
|
||||||
@@ -49,14 +54,12 @@ jobs:
|
|||||||
virtualenvs-create: true
|
virtualenvs-create: true
|
||||||
virtualenvs-in-project: true
|
virtualenvs-in-project: true
|
||||||
|
|
||||||
- name: Display Python version
|
|
||||||
run: poetry run python -c "import sys; print(sys.version)"
|
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
poetry install
|
poetry install
|
||||||
|
|
||||||
- name: Type checking
|
- name: Type checking
|
||||||
|
if: ${{ env.PYTHON_VERSION != '3.7' }}
|
||||||
run: |
|
run: |
|
||||||
poetry run mypy pyModeS
|
poetry run mypy pyModeS
|
||||||
|
|
||||||
@@ -65,6 +68,7 @@ jobs:
|
|||||||
poetry run pytest tests --cov --cov-report term-missing
|
poetry run pytest tests --cov --cov-report term-missing
|
||||||
|
|
||||||
- name: Upload coverage to Codecov
|
- name: Upload coverage to Codecov
|
||||||
uses: codecov/codecov-action@v3
|
if: ${{ github.event_name != 'pull_request_target' && env.PYTHON_VERSION == '3.10' }}
|
||||||
|
uses: codecov/codecov-action@v2
|
||||||
with:
|
with:
|
||||||
env_vars: PYTHON_VERSION
|
env_vars: PYTHON_VERSION
|
||||||
|
|||||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -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/
|
||||||
|
|
||||||
|
|||||||
34
build.py
34
build.py
@@ -1,10 +1,9 @@
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
from distutils.command import build_ext
|
|
||||||
from distutils.core import Distribution, Extension
|
from distutils.core import Distribution, Extension
|
||||||
|
from distutils.command import build_ext
|
||||||
|
|
||||||
# import pip
|
|
||||||
from Cython.Build import cythonize
|
from Cython.Build import cythonize
|
||||||
|
|
||||||
|
|
||||||
@@ -12,7 +11,16 @@ def build() -> None:
|
|||||||
compile_args = []
|
compile_args = []
|
||||||
|
|
||||||
if sys.platform == "linux":
|
if sys.platform == "linux":
|
||||||
compile_args += ["-Wno-pointer-sign", "-Wno-unused-variable"]
|
compile_args += [
|
||||||
|
"-march=native",
|
||||||
|
"-O3",
|
||||||
|
"-msse",
|
||||||
|
"-msse2",
|
||||||
|
"-mfma",
|
||||||
|
"-mfpmath=sse",
|
||||||
|
"-Wno-pointer-sign",
|
||||||
|
"-Wno-unused-variable",
|
||||||
|
]
|
||||||
|
|
||||||
extensions = [
|
extensions = [
|
||||||
Extension(
|
Extension(
|
||||||
@@ -29,16 +37,16 @@ def build() -> None:
|
|||||||
extra_compile_args=compile_args,
|
extra_compile_args=compile_args,
|
||||||
include_dirs=["pyModeS/decoder/flarm"],
|
include_dirs=["pyModeS/decoder/flarm"],
|
||||||
),
|
),
|
||||||
# Extension(
|
Extension(
|
||||||
# "pyModeS.extra.demod2400.core",
|
"pyModeS.extra.demod2400.core",
|
||||||
# [
|
[
|
||||||
# "pyModeS/extra/demod2400/core.pyx",
|
"pyModeS/extra/demod2400/core.pyx",
|
||||||
# "pyModeS/extra/demod2400/demod2400.c",
|
"pyModeS/extra/demod2400/demod2400.c",
|
||||||
# ],
|
],
|
||||||
# extra_compile_args=compile_args,
|
extra_compile_args=compile_args,
|
||||||
# include_dirs=["pyModeS/extra/demod2400"],
|
include_dirs=["pyModeS/extra/demod2400"],
|
||||||
# libraries=["m"],
|
libraries=["m"],
|
||||||
# ),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
ext_modules = cythonize(
|
ext_modules = cythonize(
|
||||||
|
|||||||
1015
poetry.lock
generated
1015
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -82,7 +82,7 @@ def airborne_position(
|
|||||||
if lon > 180:
|
if lon > 180:
|
||||||
lon = lon - 360
|
lon = lon - 360
|
||||||
|
|
||||||
return lat, lon
|
return round(lat, 5), round(lon, 5)
|
||||||
|
|
||||||
|
|
||||||
def airborne_position_with_ref(
|
def airborne_position_with_ref(
|
||||||
@@ -129,7 +129,7 @@ def airborne_position_with_ref(
|
|||||||
|
|
||||||
lon = d_lon * (m + cprlon)
|
lon = d_lon * (m + cprlon)
|
||||||
|
|
||||||
return lat, lon
|
return round(lat, 5), round(lon, 5)
|
||||||
|
|
||||||
|
|
||||||
def altitude(msg: str) -> None | int:
|
def altitude(msg: str) -> None | int:
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ def surface_position(
|
|||||||
lat_ref: float,
|
lat_ref: float,
|
||||||
lon_ref: float,
|
lon_ref: float,
|
||||||
) -> None | tuple[float, float]:
|
) -> None | tuple[float, float]:
|
||||||
|
|
||||||
"""Decode surface position from a pair of even and odd position message,
|
"""Decode surface position from a pair of even and odd position message,
|
||||||
the lat/lon of receiver must be provided to yield the correct solution.
|
the lat/lon of receiver must be provided to yield the correct solution.
|
||||||
|
|
||||||
@@ -91,7 +92,7 @@ def surface_position(
|
|||||||
imin = min(range(4), key=dls.__getitem__)
|
imin = min(range(4), key=dls.__getitem__)
|
||||||
lon = lons[imin]
|
lon = lons[imin]
|
||||||
|
|
||||||
return lat, lon
|
return round(lat, 5), round(lon, 5)
|
||||||
|
|
||||||
|
|
||||||
def surface_position_with_ref(
|
def surface_position_with_ref(
|
||||||
@@ -138,12 +139,12 @@ def surface_position_with_ref(
|
|||||||
|
|
||||||
lon = d_lon * (m + cprlon)
|
lon = d_lon * (m + cprlon)
|
||||||
|
|
||||||
return lat, lon
|
return round(lat, 5), round(lon, 5)
|
||||||
|
|
||||||
|
|
||||||
def surface_velocity(
|
def surface_velocity(
|
||||||
msg: str, source: bool = False
|
msg: str, source: bool = False
|
||||||
) -> tuple[None | float, None | float, int, str]:
|
) -> tuple[None | float, float, int, str]:
|
||||||
"""Decode surface velocity from a surface position message
|
"""Decode surface velocity from a surface position message
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -172,6 +173,7 @@ def surface_velocity(
|
|||||||
trk_status = int(mb[12])
|
trk_status = int(mb[12])
|
||||||
if trk_status == 1:
|
if trk_status == 1:
|
||||||
trk = common.bin2int(mb[13:20]) * 360 / 128
|
trk = common.bin2int(mb[13:20]) * 360 / 128
|
||||||
|
trk = round(trk, 1)
|
||||||
else:
|
else:
|
||||||
trk = None
|
trk = None
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ def airborne_velocity(
|
|||||||
spd: None | float
|
spd: None | float
|
||||||
|
|
||||||
if subtype in (1, 2):
|
if subtype in (1, 2):
|
||||||
|
|
||||||
v_ew = common.bin2int(mb[14:24])
|
v_ew = common.bin2int(mb[14:24])
|
||||||
v_ns = common.bin2int(mb[25:35])
|
v_ns = common.bin2int(mb[25:35])
|
||||||
|
|
||||||
@@ -76,7 +77,7 @@ def airborne_velocity(
|
|||||||
trk = math.degrees(trk) # convert to degrees
|
trk = math.degrees(trk) # convert to degrees
|
||||||
trk = trk if trk >= 0 else trk + 360 # no negative val
|
trk = trk if trk >= 0 else trk + 360 # no negative val
|
||||||
|
|
||||||
trk_or_hdg = trk
|
trk_or_hdg = round(trk, 2)
|
||||||
|
|
||||||
spd_type = "GS"
|
spd_type = "GS"
|
||||||
dir_type = "TRUE_NORTH"
|
dir_type = "TRUE_NORTH"
|
||||||
@@ -86,6 +87,7 @@ def airborne_velocity(
|
|||||||
hdg = None
|
hdg = None
|
||||||
else:
|
else:
|
||||||
hdg = common.bin2int(mb[14:24]) / 1024 * 360.0
|
hdg = common.bin2int(mb[14:24]) / 1024 * 360.0
|
||||||
|
hdg = round(hdg, 2)
|
||||||
|
|
||||||
trk_or_hdg = hdg
|
trk_or_hdg = hdg
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ def wind44(msg: str) -> Tuple[Optional[int], Optional[float]]:
|
|||||||
speed = common.bin2int(d[5:14]) # knots
|
speed = common.bin2int(d[5:14]) # knots
|
||||||
direction = common.bin2int(d[14:23]) * 180 / 256 # degree
|
direction = common.bin2int(d[14:23]) * 180 / 256 # degree
|
||||||
|
|
||||||
return speed, direction
|
return round(speed, 0), round(direction, 1)
|
||||||
|
|
||||||
|
|
||||||
def temp44(msg: str) -> Tuple[float, float]:
|
def temp44(msg: str) -> Tuple[float, float]:
|
||||||
@@ -96,8 +96,10 @@ def temp44(msg: str) -> Tuple[float, float]:
|
|||||||
value = value - 1024
|
value = value - 1024
|
||||||
|
|
||||||
temp = value * 0.25 # celsius
|
temp = value * 0.25 # celsius
|
||||||
|
temp = round(temp, 2)
|
||||||
|
|
||||||
temp_alternative = value * 0.125 # celsius
|
temp_alternative = value * 0.125 # celsius
|
||||||
|
temp_alternative = round(temp_alternative, 3)
|
||||||
|
|
||||||
return temp, temp_alternative
|
return temp, temp_alternative
|
||||||
|
|
||||||
@@ -138,7 +140,7 @@ def hum44(msg: str) -> Optional[float]:
|
|||||||
|
|
||||||
hm = common.bin2int(d[50:56]) * 100 / 64 # %
|
hm = common.bin2int(d[50:56]) * 100 / 64 # %
|
||||||
|
|
||||||
return hm
|
return round(hm, 1)
|
||||||
|
|
||||||
|
|
||||||
def turb44(msg: str) -> Optional[int]:
|
def turb44(msg: str) -> Optional[int]:
|
||||||
|
|||||||
@@ -171,6 +171,7 @@ def temp45(msg: str) -> Optional[float]:
|
|||||||
value = value - 512
|
value = value - 512
|
||||||
|
|
||||||
temp = value * 0.25 # celsius
|
temp = value * 0.25 # celsius
|
||||||
|
temp = round(temp, 1)
|
||||||
|
|
||||||
return temp
|
return temp
|
||||||
|
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ def roll50(msg: str) -> Optional[float]:
|
|||||||
value = value - 512
|
value = value - 512
|
||||||
|
|
||||||
angle = value * 45 / 256 # degree
|
angle = value * 45 / 256 # degree
|
||||||
return angle
|
return round(angle, 1)
|
||||||
|
|
||||||
|
|
||||||
def trk50(msg: str) -> Optional[float]:
|
def trk50(msg: str) -> Optional[float]:
|
||||||
@@ -110,7 +110,7 @@ def trk50(msg: str) -> Optional[float]:
|
|||||||
if trk < 0:
|
if trk < 0:
|
||||||
trk = 360 + trk
|
trk = 360 + trk
|
||||||
|
|
||||||
return trk
|
return round(trk, 3)
|
||||||
|
|
||||||
|
|
||||||
def gs50(msg: str) -> Optional[float]:
|
def gs50(msg: str) -> Optional[float]:
|
||||||
@@ -154,7 +154,7 @@ def rtrk50(msg: str) -> Optional[float]:
|
|||||||
value = value - 512
|
value = value - 512
|
||||||
|
|
||||||
angle = value * 8 / 256 # degree / sec
|
angle = value * 8 / 256 # degree / sec
|
||||||
return angle
|
return round(angle, 3)
|
||||||
|
|
||||||
|
|
||||||
def tas50(msg: str) -> Optional[float]:
|
def tas50(msg: str) -> Optional[float]:
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ def hdg53(msg: str) -> Optional[float]:
|
|||||||
if hdg < 0:
|
if hdg < 0:
|
||||||
hdg = 360 + hdg
|
hdg = 360 + hdg
|
||||||
|
|
||||||
return hdg
|
return round(hdg, 3)
|
||||||
|
|
||||||
|
|
||||||
def ias53(msg: str) -> Optional[float]:
|
def ias53(msg: str) -> Optional[float]:
|
||||||
@@ -122,7 +122,7 @@ def mach53(msg: str) -> Optional[float]:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
mach = common.bin2int(d[24:33]) * 0.008
|
mach = common.bin2int(d[24:33]) * 0.008
|
||||||
return mach
|
return round(mach, 3)
|
||||||
|
|
||||||
|
|
||||||
def tas53(msg: str) -> Optional[float]:
|
def tas53(msg: str) -> Optional[float]:
|
||||||
@@ -140,7 +140,7 @@ def tas53(msg: str) -> Optional[float]:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
tas = common.bin2int(d[34:46]) * 0.5 # kts
|
tas = common.bin2int(d[34:46]) * 0.5 # kts
|
||||||
return tas
|
return round(tas, 1)
|
||||||
|
|
||||||
|
|
||||||
def vr53(msg: str) -> Optional[int]:
|
def vr53(msg: str) -> Optional[int]:
|
||||||
|
|||||||
@@ -200,6 +200,7 @@ def selected_heading(msg: str) -> None | float:
|
|||||||
else:
|
else:
|
||||||
hdg_sign = int(mb[30])
|
hdg_sign = int(mb[30])
|
||||||
hdg = (hdg_sign + 1) * common.bin2int(mb[31:39]) * (180 / 256)
|
hdg = (hdg_sign + 1) * common.bin2int(mb[31:39]) * (180 / 256)
|
||||||
|
hdg = round(hdg, 2)
|
||||||
|
|
||||||
return hdg
|
return hdg
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
from typing import TypedDict
|
from typing import TypedDict
|
||||||
from typing_extensions import Annotated
|
|
||||||
|
|
||||||
from .decode import flarm as flarm_decode
|
from .decode import flarm as flarm_decode
|
||||||
|
|
||||||
@@ -11,8 +10,8 @@ class DecodedMessage(TypedDict):
|
|||||||
icao24: str
|
icao24: str
|
||||||
latitude: float
|
latitude: float
|
||||||
longitude: float
|
longitude: float
|
||||||
altitude: Annotated[int, "m"]
|
altitude: int
|
||||||
vertical_speed: Annotated[float, "m/s"]
|
vertical_speed: float
|
||||||
groundspeed: int
|
groundspeed: int
|
||||||
track: int
|
track: int
|
||||||
type: str
|
type: str
|
||||||
|
|||||||
@@ -130,12 +130,12 @@ def flarm(long timestamp, str msg, float refLat, float refLon, **kwargs):
|
|||||||
return dict(
|
return dict(
|
||||||
timestamp=timestamp,
|
timestamp=timestamp,
|
||||||
icao24=icao24,
|
icao24=icao24,
|
||||||
latitude=lat * 1e-7,
|
latitude=round(lat * 1e-7, 6),
|
||||||
longitude=lon * 1e-7,
|
longitude=round(lon * 1e-7, 6),
|
||||||
geoaltitude=altitude,
|
geoaltitude=altitude,
|
||||||
vertical_speed=raw_vs * mult_factor / 10,
|
vertical_speed=raw_vs * mult_factor / 10,
|
||||||
groundspeed=speed,
|
groundspeed=round(speed),
|
||||||
track=heading4 - 4 * turningRate(heading4, heading8) / 4,
|
track=round(heading4 - 4 * turningRate(heading4, heading8) / 4),
|
||||||
type=AIRCRAFT_TYPES[aircraft_type],
|
type=AIRCRAFT_TYPES[aircraft_type],
|
||||||
sensorLatitude=refLat,
|
sensorLatitude=refLat,
|
||||||
sensorLongitude=refLon,
|
sensorLongitude=refLon,
|
||||||
|
|||||||
3
pyModeS/extra/demod2400/__init__.py
Normal file
3
pyModeS/extra/demod2400/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from .core import demod2400
|
||||||
|
|
||||||
|
__all__ = ["demod2400"]
|
||||||
4
pyModeS/extra/demod2400/core.pyi
Normal file
4
pyModeS/extra/demod2400/core.pyi
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import numpy as np
|
||||||
|
import numpy.typing as npt
|
||||||
|
|
||||||
|
def demod2400(data: npt.NDArray[np.uint16], timestamp: float): ...
|
||||||
49
pyModeS/extra/demod2400/core.pyx
Normal file
49
pyModeS/extra/demod2400/core.pyx
Normal file
@@ -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
|
||||||
256
pyModeS/extra/demod2400/demod2400.c
Normal file
256
pyModeS/extra/demod2400/demod2400.c
Normal file
@@ -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;
|
||||||
|
}
|
||||||
11
pyModeS/extra/demod2400/demod2400.h
Normal file
11
pyModeS/extra/demod2400/demod2400.h
Normal file
@@ -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
|
||||||
133
pyModeS/extra/demod2400/rtlreader.py
Normal file
133
pyModeS/extra/demod2400/rtlreader.py
Normal file
@@ -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,22 +1,14 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pyModeS as pms
|
import pyModeS as pms
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
|
|
||||||
import_msg = """
|
|
||||||
---------------------------------------------------------------------
|
|
||||||
Warning: pyrtlsdr not installed (required for using RTL-SDR devices)!
|
|
||||||
---------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import rtlsdr # type: ignore
|
import rtlsdr # type: ignore
|
||||||
except ImportError:
|
except:
|
||||||
print(import_msg)
|
print("------------------------------------------------------------------------")
|
||||||
|
print("! Warning: pyrtlsdr not installed (required for using RTL-SDR devices) !")
|
||||||
|
print("------------------------------------------------------------------------")
|
||||||
|
|
||||||
sampling_rate = 2e6
|
sampling_rate = 2e6
|
||||||
smaples_per_microsec = 2
|
smaples_per_microsec = 2
|
||||||
@@ -32,9 +24,9 @@ th_amp_diff = 0.8 # signal amplitude threshold difference between 0 and 1 bit
|
|||||||
|
|
||||||
|
|
||||||
class RtlReader(object):
|
class RtlReader(object):
|
||||||
def __init__(self, **kwargs) -> None:
|
def __init__(self, **kwargs):
|
||||||
super(RtlReader, self).__init__()
|
super(RtlReader, self).__init__()
|
||||||
self.signal_buffer: list[float] = [] # amplitude of the sample only
|
self.signal_buffer = [] # amplitude of the sample only
|
||||||
self.sdr = rtlsdr.RtlSdr()
|
self.sdr = rtlsdr.RtlSdr()
|
||||||
self.sdr.sample_rate = sampling_rate
|
self.sdr.sample_rate = sampling_rate
|
||||||
self.sdr.center_freq = modes_frequency
|
self.sdr.center_freq = modes_frequency
|
||||||
@@ -47,7 +39,7 @@ class RtlReader(object):
|
|||||||
|
|
||||||
self.exception_queue = None
|
self.exception_queue = None
|
||||||
|
|
||||||
def _calc_noise(self) -> float:
|
def _calc_noise(self):
|
||||||
"""Calculate noise floor"""
|
"""Calculate noise floor"""
|
||||||
window = smaples_per_microsec * 100
|
window = smaples_per_microsec * 100
|
||||||
total_len = len(self.signal_buffer)
|
total_len = len(self.signal_buffer)
|
||||||
@@ -58,7 +50,7 @@ class RtlReader(object):
|
|||||||
)
|
)
|
||||||
return min(means)
|
return min(means)
|
||||||
|
|
||||||
def _process_buffer(self) -> list[list[Any]]:
|
def _process_buffer(self):
|
||||||
"""process raw IQ data in the buffer"""
|
"""process raw IQ data in the buffer"""
|
||||||
|
|
||||||
# update noise floor
|
# update noise floor
|
||||||
@@ -78,18 +70,17 @@ class RtlReader(object):
|
|||||||
i += 1
|
i += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
frame_start = i + pbits * 2
|
if self._check_preamble(self.signal_buffer[i : i + pbits * 2]):
|
||||||
if self._check_preamble(self.signal_buffer[i:frame_start]):
|
frame_start = i + pbits * 2
|
||||||
|
frame_end = i + pbits * 2 + (fbits + 1) * 2
|
||||||
frame_length = (fbits + 1) * 2
|
frame_length = (fbits + 1) * 2
|
||||||
frame_end = frame_start + frame_length
|
|
||||||
frame_pulses = self.signal_buffer[frame_start:frame_end]
|
frame_pulses = self.signal_buffer[frame_start:frame_end]
|
||||||
|
|
||||||
threshold = max(frame_pulses) * 0.2
|
threshold = max(frame_pulses) * 0.2
|
||||||
|
|
||||||
msgbin: list[int] = []
|
msgbin = []
|
||||||
for j in range(0, frame_length, 2):
|
for j in range(0, frame_length, 2):
|
||||||
j_2 = j + 2
|
p2 = frame_pulses[j : j + 2]
|
||||||
p2 = frame_pulses[j:j_2]
|
|
||||||
if len(p2) < 2:
|
if len(p2) < 2:
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -126,7 +117,7 @@ class RtlReader(object):
|
|||||||
|
|
||||||
return messages
|
return messages
|
||||||
|
|
||||||
def _check_preamble(self, pulses) -> bool:
|
def _check_preamble(self, pulses):
|
||||||
if len(pulses) != 16:
|
if len(pulses) != 16:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -136,7 +127,7 @@ class RtlReader(object):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _check_msg(self, msg) -> bool:
|
def _check_msg(self, msg):
|
||||||
df = pms.df(msg)
|
df = pms.df(msg)
|
||||||
msglen = len(msg)
|
msglen = len(msg)
|
||||||
if df == 17 and msglen == 28:
|
if df == 17 and msglen == 28:
|
||||||
@@ -146,9 +137,8 @@ class RtlReader(object):
|
|||||||
return True
|
return True
|
||||||
elif df in [4, 5, 11] and msglen == 14:
|
elif df in [4, 5, 11] and msglen == 14:
|
||||||
return True
|
return True
|
||||||
return False
|
|
||||||
|
|
||||||
def _debug_msg(self, msg) -> None:
|
def _debug_msg(self, msg):
|
||||||
df = pms.df(msg)
|
df = pms.df(msg)
|
||||||
msglen = len(msg)
|
msglen = len(msg)
|
||||||
if df == 17 and msglen == 28:
|
if df == 17 and msglen == 28:
|
||||||
@@ -161,7 +151,7 @@ class RtlReader(object):
|
|||||||
# print("[*]", msg)
|
# print("[*]", msg)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _read_callback(self, data, rtlsdr_obj) -> None:
|
def _read_callback(self, data, rtlsdr_obj):
|
||||||
amp = np.absolute(data)
|
amp = np.absolute(data)
|
||||||
self.signal_buffer.extend(amp.tolist())
|
self.signal_buffer.extend(amp.tolist())
|
||||||
|
|
||||||
@@ -169,18 +159,16 @@ class RtlReader(object):
|
|||||||
messages = self._process_buffer()
|
messages = self._process_buffer()
|
||||||
self.handle_messages(messages)
|
self.handle_messages(messages)
|
||||||
|
|
||||||
def handle_messages(self, messages) -> None:
|
def handle_messages(self, messages):
|
||||||
"""re-implement this method to handle the messages"""
|
"""re-implement this method to handle the messages"""
|
||||||
for msg, t in messages:
|
for msg, t in messages:
|
||||||
# print("%15.9f %s" % (t, msg))
|
# print("%15.9f %s" % (t, msg))
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def stop(self, *args, **kwargs) -> None:
|
def stop(self, *args, **kwargs):
|
||||||
self.sdr.close()
|
self.sdr.close()
|
||||||
|
|
||||||
def run(
|
def run(self, raw_pipe_in=None, stop_flag=None, exception_queue=None):
|
||||||
self, raw_pipe_in=None, stop_flag=None, exception_queue=None
|
|
||||||
) -> None:
|
|
||||||
self.raw_pipe_in = raw_pipe_in
|
self.raw_pipe_in = raw_pipe_in
|
||||||
self.exception_queue = exception_queue
|
self.exception_queue = exception_queue
|
||||||
self.stop_flag = stop_flag
|
self.stop_flag = stop_flag
|
||||||
|
|||||||
@@ -231,7 +231,7 @@ def squawk(binstr: str) -> str:
|
|||||||
binstr (String): 13 bits binary string
|
binstr (String): 13 bits binary string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
string: squawk code
|
int: altitude in ft
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if len(binstr) != 13 or not set(binstr).issubset(set("01")):
|
if len(binstr) != 13 or not set(binstr).issubset(set("01")):
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import signal
|
|||||||
import multiprocessing
|
import multiprocessing
|
||||||
from pyModeS.streamer.decode import Decode
|
from pyModeS.streamer.decode import Decode
|
||||||
from pyModeS.streamer.screen import Screen
|
from pyModeS.streamer.screen import Screen
|
||||||
from pyModeS.streamer.source import NetSource, RtlSdrSource # , RtlSdrSource24
|
from pyModeS.streamer.source import NetSource, RtlSdrSource, RtlSdrSource24
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@@ -100,8 +100,8 @@ def main():
|
|||||||
source = NetSource(host=SERVER, port=PORT, rawtype=DATATYPE)
|
source = NetSource(host=SERVER, port=PORT, rawtype=DATATYPE)
|
||||||
elif SOURCE == "rtlsdr":
|
elif SOURCE == "rtlsdr":
|
||||||
source = RtlSdrSource()
|
source = RtlSdrSource()
|
||||||
# elif SOURCE == "rtlsdr24":
|
elif SOURCE == "rtlsdr24":
|
||||||
# source = RtlSdrSource24()
|
source = RtlSdrSource24()
|
||||||
|
|
||||||
recv_process = multiprocessing.Process(
|
recv_process = multiprocessing.Process(
|
||||||
target=source.run, args=(raw_pipe_in, stop_flag, exception_queue)
|
target=source.run, args=(raw_pipe_in, stop_flag, exception_queue)
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "pyModeS"
|
name = "pyModeS"
|
||||||
version = "2.16"
|
version = "2.11"
|
||||||
description = "Python Mode-S and ADS-B Decoder"
|
description = "Python Mode-S and ADS-B Decoder"
|
||||||
authors = ["Junzi Sun <j.sun-1@tudelft.nl>"]
|
authors = ["Junzi Sun <j.sun-1@tudelft.nl>"]
|
||||||
license = "GNU GPL v3"
|
license = "GNU GPL v3"
|
||||||
@@ -22,7 +22,7 @@ include = [
|
|||||||
"*.pxd",
|
"*.pxd",
|
||||||
"*.pyi",
|
"*.pyi",
|
||||||
"py.typed",
|
"py.typed",
|
||||||
{ path = "pyModeS/**/*.so", format = "wheel" }
|
{ path = "src/pyModeS/**/*.so", format = "wheel" }
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.poetry.build]
|
[tool.poetry.build]
|
||||||
@@ -39,6 +39,7 @@ pyzmq = "^24.0"
|
|||||||
pyrtlsdr = {version = "^0.2.93", optional = true}
|
pyrtlsdr = {version = "^0.2.93", optional = true}
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
|
Cython = "^0.29.32"
|
||||||
mypy = "^0.991"
|
mypy = "^0.991"
|
||||||
flake8 = "^5.0.0"
|
flake8 = "^5.0.0"
|
||||||
black = "^22.12.0"
|
black = "^22.12.0"
|
||||||
@@ -46,6 +47,7 @@ isort = "^5.11.4"
|
|||||||
pytest = "^7.2.0"
|
pytest = "^7.2.0"
|
||||||
pytest-cov = "^4.0.0"
|
pytest-cov = "^4.0.0"
|
||||||
codecov = "^2.1.12"
|
codecov = "^2.1.12"
|
||||||
|
ipykernel = "^6.20.0"
|
||||||
|
|
||||||
[tool.poetry.extras]
|
[tool.poetry.extras]
|
||||||
rtlsdr = ["pyrtlsdr"]
|
rtlsdr = ["pyrtlsdr"]
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
from pyModeS import adsb
|
from pyModeS import adsb
|
||||||
from pytest import approx
|
|
||||||
|
|
||||||
# === TEST ADS-B package ===
|
# === TEST ADS-B package ===
|
||||||
|
|
||||||
@@ -23,7 +22,7 @@ def test_adsb_position():
|
|||||||
1446332400,
|
1446332400,
|
||||||
1446332405,
|
1446332405,
|
||||||
)
|
)
|
||||||
assert pos == (approx(49.81755, 0.001), approx(6.08442, 0.001))
|
assert pos == (49.81755, 6.08442)
|
||||||
|
|
||||||
|
|
||||||
def test_adsb_position_swap_odd_even():
|
def test_adsb_position_swap_odd_even():
|
||||||
@@ -33,32 +32,32 @@ def test_adsb_position_swap_odd_even():
|
|||||||
1446332405,
|
1446332405,
|
||||||
1446332400,
|
1446332400,
|
||||||
)
|
)
|
||||||
assert pos == (approx(49.81755, 0.001), approx(6.08442, 0.001))
|
assert pos == (49.81755, 6.08442)
|
||||||
|
|
||||||
|
|
||||||
def test_adsb_position_with_ref():
|
def test_adsb_position_with_ref():
|
||||||
pos = adsb.position_with_ref("8D40058B58C901375147EFD09357", 49.0, 6.0)
|
pos = adsb.position_with_ref("8D40058B58C901375147EFD09357", 49.0, 6.0)
|
||||||
assert pos == (approx(49.82410, 0.001), approx(6.06785, 0.001))
|
assert pos == (49.82410, 6.06785)
|
||||||
pos = adsb.position_with_ref("8FC8200A3AB8F5F893096B000000", -43.5, 172.5)
|
pos = adsb.position_with_ref("8FC8200A3AB8F5F893096B000000", -43.5, 172.5)
|
||||||
assert pos == (approx(-43.48564, 0.001), approx(172.53942, 0.001))
|
assert pos == (-43.48564, 172.53942)
|
||||||
|
|
||||||
|
|
||||||
def test_adsb_airborne_position_with_ref():
|
def test_adsb_airborne_position_with_ref():
|
||||||
pos = adsb.airborne_position_with_ref(
|
pos = adsb.airborne_position_with_ref(
|
||||||
"8D40058B58C901375147EFD09357", 49.0, 6.0
|
"8D40058B58C901375147EFD09357", 49.0, 6.0
|
||||||
)
|
)
|
||||||
assert pos == (approx(49.82410, 0.001), approx(6.06785, 0.001))
|
assert pos == (49.82410, 6.06785)
|
||||||
pos = adsb.airborne_position_with_ref(
|
pos = adsb.airborne_position_with_ref(
|
||||||
"8D40058B58C904A87F402D3B8C59", 49.0, 6.0
|
"8D40058B58C904A87F402D3B8C59", 49.0, 6.0
|
||||||
)
|
)
|
||||||
assert pos == (approx(49.81755, 0.001), approx(6.08442, 0.001))
|
assert pos == (49.81755, 6.08442)
|
||||||
|
|
||||||
|
|
||||||
def test_adsb_surface_position_with_ref():
|
def test_adsb_surface_position_with_ref():
|
||||||
pos = adsb.surface_position_with_ref(
|
pos = adsb.surface_position_with_ref(
|
||||||
"8FC8200A3AB8F5F893096B000000", -43.5, 172.5
|
"8FC8200A3AB8F5F893096B000000", -43.5, 172.5
|
||||||
)
|
)
|
||||||
assert pos == (approx(-43.48564, 0.001), approx(172.53942, 0.001))
|
assert pos == (-43.48564, 172.53942)
|
||||||
|
|
||||||
|
|
||||||
def test_adsb_surface_position():
|
def test_adsb_surface_position():
|
||||||
@@ -70,7 +69,7 @@ def test_adsb_surface_position():
|
|||||||
-43.496,
|
-43.496,
|
||||||
172.558,
|
172.558,
|
||||||
)
|
)
|
||||||
assert pos == (approx(-43.48564, 0.001), approx(172.53942, 0.001))
|
assert pos == (-43.48564, 172.53942)
|
||||||
|
|
||||||
|
|
||||||
def test_adsb_alt():
|
def test_adsb_alt():
|
||||||
@@ -81,9 +80,9 @@ def test_adsb_velocity():
|
|||||||
vgs = adsb.velocity("8D485020994409940838175B284F")
|
vgs = adsb.velocity("8D485020994409940838175B284F")
|
||||||
vas = adsb.velocity("8DA05F219B06B6AF189400CBC33F")
|
vas = adsb.velocity("8DA05F219B06B6AF189400CBC33F")
|
||||||
vgs_surface = adsb.velocity("8FC8200A3AB8F5F893096B000000")
|
vgs_surface = adsb.velocity("8FC8200A3AB8F5F893096B000000")
|
||||||
assert vgs == (159, approx(182.88, 0.1), -832, "GS")
|
assert vgs == (159, 182.88, -832, "GS")
|
||||||
assert vas == (375, approx(243.98, 0.1), -2304, "TAS")
|
assert vas == (375, 243.98, -2304, "TAS")
|
||||||
assert vgs_surface == (19, approx(42.2, 0.1), 0, "GS")
|
assert vgs_surface == (19, 42.2, 0, "GS")
|
||||||
assert adsb.altitude_diff("8D485020994409940838175B284F") == 550
|
assert adsb.altitude_diff("8D485020994409940838175B284F") == 550
|
||||||
|
|
||||||
|
|
||||||
@@ -97,9 +96,7 @@ def test_adsb_target_state_status():
|
|||||||
sel_alt = adsb.selected_altitude("8DA05629EA21485CBF3F8CADAEEB")
|
sel_alt = adsb.selected_altitude("8DA05629EA21485CBF3F8CADAEEB")
|
||||||
assert sel_alt == (16992, "MCP/FCU")
|
assert sel_alt == (16992, "MCP/FCU")
|
||||||
assert adsb.baro_pressure_setting("8DA05629EA21485CBF3F8CADAEEB") == 1012.8
|
assert adsb.baro_pressure_setting("8DA05629EA21485CBF3F8CADAEEB") == 1012.8
|
||||||
assert adsb.selected_heading("8DA05629EA21485CBF3F8CADAEEB") == approx(
|
assert adsb.selected_heading("8DA05629EA21485CBF3F8CADAEEB") == 66.8
|
||||||
66.8, 0.1
|
|
||||||
)
|
|
||||||
assert adsb.autopilot("8DA05629EA21485CBF3F8CADAEEB") is True
|
assert adsb.autopilot("8DA05629EA21485CBF3F8CADAEEB") is True
|
||||||
assert adsb.vnav_mode("8DA05629EA21485CBF3F8CADAEEB") is True
|
assert adsb.vnav_mode("8DA05629EA21485CBF3F8CADAEEB") is True
|
||||||
assert adsb.altitude_hold_mode("8DA05629EA21485CBF3F8CADAEEB") is False
|
assert adsb.altitude_hold_mode("8DA05629EA21485CBF3F8CADAEEB") is False
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from pyModeS import bds, commb
|
from pyModeS import bds, commb
|
||||||
from pytest import approx
|
import pytest
|
||||||
|
|
||||||
# from pyModeS import ehs, els # deprecated
|
# from pyModeS import ehs, els # deprecated
|
||||||
|
|
||||||
@@ -23,24 +23,32 @@ def test_bds40_functions():
|
|||||||
|
|
||||||
|
|
||||||
def test_bds50_functions():
|
def test_bds50_functions():
|
||||||
msg1 = "A000139381951536E024D4CCF6B5"
|
assert bds.bds50.roll50("A000139381951536E024D4CCF6B5") == 2.1
|
||||||
msg2 = "A0001691FFD263377FFCE02B2BF9"
|
assert bds.bds50.roll50("A0001691FFD263377FFCE02B2BF9") == -0.4
|
||||||
|
assert bds.bds50.trk50("A000139381951536E024D4CCF6B5") == 114.258
|
||||||
|
assert bds.bds50.gs50("A000139381951536E024D4CCF6B5") == 438
|
||||||
|
assert bds.bds50.rtrk50("A000139381951536E024D4CCF6B5") == 0.125
|
||||||
|
assert bds.bds50.tas50("A000139381951536E024D4CCF6B5") == 424
|
||||||
|
|
||||||
for module in [bds.bds50, commb]:
|
assert commb.roll50("A000139381951536E024D4CCF6B5") == 2.1
|
||||||
assert module.roll50(msg1) == approx(2.1, 0.01)
|
assert commb.roll50("A0001691FFD263377FFCE02B2BF9") == -0.4 # signed value
|
||||||
assert module.roll50(msg2) == approx(-0.35, 0.01) # signed value
|
assert commb.trk50("A000139381951536E024D4CCF6B5") == 114.258
|
||||||
assert module.trk50(msg1) == approx(114.258, 0.1)
|
assert commb.gs50("A000139381951536E024D4CCF6B5") == 438
|
||||||
assert module.gs50(msg1) == 438
|
assert commb.rtrk50("A000139381951536E024D4CCF6B5") == 0.125
|
||||||
assert module.rtrk50(msg1) == 0.125
|
assert commb.tas50("A000139381951536E024D4CCF6B5") == 424
|
||||||
assert module.tas50(msg1) == 424
|
|
||||||
|
|
||||||
|
|
||||||
def test_bds60_functions():
|
def test_bds60_functions():
|
||||||
msg = "A00004128F39F91A7E27C46ADC21"
|
msg = "A00004128F39F91A7E27C46ADC21"
|
||||||
|
|
||||||
for module in [bds.bds60, commb]:
|
assert bds.bds60.hdg60(msg) == pytest.approx(42.71484)
|
||||||
assert bds.bds60.hdg60(msg) == approx(42.71484)
|
assert bds.bds60.ias60(msg) == 252
|
||||||
assert bds.bds60.ias60(msg) == 252
|
assert bds.bds60.mach60(msg) == 0.42
|
||||||
assert bds.bds60.mach60(msg) == 0.42
|
assert bds.bds60.vr60baro(msg) == -1920
|
||||||
assert bds.bds60.vr60baro(msg) == -1920
|
assert bds.bds60.vr60ins(msg) == -1920
|
||||||
assert bds.bds60.vr60ins(msg) == -1920
|
|
||||||
|
assert commb.hdg60(msg) == pytest.approx(42.71484)
|
||||||
|
assert commb.ias60(msg) == 252
|
||||||
|
assert commb.mach60(msg) == 0.42
|
||||||
|
assert commb.vr60baro(msg) == -1920
|
||||||
|
assert commb.vr60ins(msg) == -1920
|
||||||
|
|||||||
Reference in New Issue
Block a user