Compare commits
53 Commits
pr_demod24
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
b4be55116c | ||
|
fdfa2b3fab | ||
|
2f5dbdf08d | ||
|
b22734ad51 | ||
|
e2de7bc9ac | ||
|
74ea61fb11 | ||
|
08218f558d | ||
|
8acbdf186c | ||
|
d8dd978730 | ||
|
01ceb8114a | ||
|
4a7c719978 | ||
|
93794b64ca | ||
|
9646c2fbea | ||
|
97a50a5b13 | ||
|
81b54accb5 | ||
|
942c68dc78 | ||
|
36fb0de92c | ||
|
582a0d2188 | ||
|
45ae55d965 | ||
|
4cf024f144 | ||
|
eea09227b0 | ||
|
852315ed2d | ||
|
77273153cb | ||
|
b4c81db151 | ||
|
b6608b2f78 | ||
|
070dc80bf4 | ||
|
8d9a8df9d9 | ||
|
458a78c30b | ||
|
57aa7a7d9c | ||
|
b1f8f6ed62 | ||
|
8e2051af68 | ||
|
50864b56aa | ||
|
8403cb61e4 | ||
|
a5b8d670d3 | ||
|
4e19ecdfa9 | ||
|
0d7a310007 | ||
|
66c29840b0 | ||
|
99dc2a4f40 | ||
|
39936e4472 | ||
|
144ee1710d | ||
|
09ed997f91 | ||
|
e437931254 | ||
|
72fcc794e7 | ||
|
ff12d819da | ||
|
dc9d7ea91a | ||
|
5bc11cd78c | ||
|
9e6f9c3ffe | ||
|
e1017bc2e5 | ||
|
ebe1f76cd9 | ||
|
8830ef3dfe | ||
|
d999851da5 | ||
|
6d0406c6b9 | ||
|
6140f84058 |
11
.github/dependabot.yml
vendored
Normal file
11
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "pip"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "monthly"
|
||||||
|
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "monthly"
|
16
.github/workflows/build-publish.yml
vendored
Normal file
16
.github/workflows/build-publish.yml
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
name: build and publish
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [created]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Build and publish to pypi
|
||||||
|
uses: JRubics/poetry-publish@v2.0
|
||||||
|
with:
|
||||||
|
poetry_version: "==1.8.2"
|
||||||
|
pypi_token: ${{ secrets.PYPI_API_TOKEN_PYMODES }}
|
29
.github/workflows/pypi-publish.yml
vendored
29
.github/workflows/pypi-publish.yml
vendored
@ -1,29 +0,0 @@
|
|||||||
# This workflows will upload a Python Package using Twine when a release is created
|
|
||||||
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
|
|
||||||
|
|
||||||
name: PyPI Publish
|
|
||||||
|
|
||||||
on:
|
|
||||||
release:
|
|
||||||
types: [created]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Set up Python
|
|
||||||
uses: actions/setup-python@v2
|
|
||||||
with:
|
|
||||||
python-version: "3.x"
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
python -m pip install --upgrade pip
|
|
||||||
pip install setuptools wheel twine
|
|
||||||
- name: Build and publish
|
|
||||||
env:
|
|
||||||
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
|
|
||||||
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
|
|
||||||
run: |
|
|
||||||
python setup.py sdist bdist_wheel
|
|
||||||
twine upload dist/*
|
|
31
.github/workflows/run-tests.yml
vendored
31
.github/workflows/run-tests.yml
vendored
@ -6,7 +6,7 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
POETRY_VERSION: "1.3.1"
|
POETRY_VERSION: "1.6.1"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
@ -14,31 +14,27 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
python-version: ["3.8", "3.9", "3.10", "3.11"]
|
python-version: ["3.9", "3.10", "3.11", "3.12"]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
PYTHON_VERSION: ${{ matrix.python-version }}
|
PYTHON_VERSION: ${{ matrix.python-version }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v5
|
||||||
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@v4
|
||||||
|
if: ${{ !startsWith(runner.os, 'windows') }}
|
||||||
with:
|
with:
|
||||||
path: ~/.local
|
path: |
|
||||||
|
~/.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
|
||||||
@ -47,18 +43,20 @@ jobs:
|
|||||||
echo "C:\Users\runneradmin\.local\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
echo "C:\Users\runneradmin\.local\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||||
|
|
||||||
- name: Install and configure Poetry
|
- name: Install and configure Poetry
|
||||||
uses: snok/install-poetry@v1.3.3
|
uses: snok/install-poetry@v1.4.0
|
||||||
with:
|
with:
|
||||||
version: ${{ env.POETRY_VERSION }}
|
version: ${{ env.POETRY_VERSION }}
|
||||||
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
|
||||||
|
|
||||||
@ -67,7 +65,6 @@ 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
|
||||||
if: ${{ github.event_name != 'pull_request_target' && env.PYTHON_VERSION == '3.10' }}
|
uses: codecov/codecov-action@v4
|
||||||
uses: codecov/codecov-action@v2
|
|
||||||
with:
|
with:
|
||||||
env_vars: PYTHON_VERSION
|
env_vars: PYTHON_VERSION
|
||||||
|
22
build.py
22
build.py
@ -1,26 +1,18 @@
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
from distutils.core import Distribution, Extension
|
|
||||||
from distutils.command import build_ext
|
|
||||||
|
|
||||||
|
# import pip
|
||||||
from Cython.Build import cythonize
|
from Cython.Build import cythonize
|
||||||
|
from setuptools import Distribution, Extension
|
||||||
|
from setuptools.command import build_ext
|
||||||
|
|
||||||
|
|
||||||
def build() -> None:
|
def build() -> None:
|
||||||
compile_args = []
|
compile_args = []
|
||||||
|
|
||||||
if sys.platform == "linux":
|
if sys.platform == "linux":
|
||||||
compile_args += [
|
compile_args += ["-Wno-pointer-sign", "-Wno-unused-variable"]
|
||||||
"-march=native",
|
|
||||||
"-O3",
|
|
||||||
"-msse",
|
|
||||||
"-msse2",
|
|
||||||
"-mfma",
|
|
||||||
"-mfpmath=sse",
|
|
||||||
"-Wno-pointer-sign",
|
|
||||||
"-Wno-unused-variable",
|
|
||||||
]
|
|
||||||
|
|
||||||
extensions = [
|
extensions = [
|
||||||
Extension(
|
Extension(
|
||||||
@ -54,9 +46,7 @@ def build() -> None:
|
|||||||
compiler_directives={"binding": True, "language_level": 3},
|
compiler_directives={"binding": True, "language_level": 3},
|
||||||
)
|
)
|
||||||
|
|
||||||
distribution = Distribution(
|
distribution = Distribution({"name": "extended", "ext_modules": ext_modules})
|
||||||
{"name": "extended", "ext_modules": ext_modules}
|
|
||||||
)
|
|
||||||
distribution.package_dir = "extended" # type: ignore
|
distribution.package_dir = "extended" # type: ignore
|
||||||
|
|
||||||
cmd = build_ext.build_ext(distribution)
|
cmd = build_ext.build_ext(distribution)
|
||||||
@ -65,7 +55,7 @@ def build() -> None:
|
|||||||
cmd.run()
|
cmd.run()
|
||||||
|
|
||||||
# Copy built extensions back to the project
|
# Copy built extensions back to the project
|
||||||
for output in cmd.get_outputs():
|
for output in cmd.get_output_mapping():
|
||||||
relative_extension = os.path.relpath(output, cmd.build_lib)
|
relative_extension = os.path.relpath(output, cmd.build_lib)
|
||||||
shutil.copyfile(output, relative_extension)
|
shutil.copyfile(output, relative_extension)
|
||||||
mode = os.stat(relative_extension).st_mode
|
mode = os.stat(relative_extension).st_mode
|
||||||
|
1607
poetry.lock
generated
1607
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
3
pyModeS/.gitignore
vendored
Normal file
3
pyModeS/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
decoder/flarm/decode.c
|
||||||
|
extra/demod2400/core.c
|
||||||
|
c_common.c
|
@ -311,7 +311,7 @@ cpdef int altitude(str binstr):
|
|||||||
|
|
||||||
if bin2int(binstr) == 0:
|
if bin2int(binstr) == 0:
|
||||||
# altitude unknown or invalid
|
# altitude unknown or invalid
|
||||||
alt = -9999
|
alt = -999999
|
||||||
|
|
||||||
elif Mbit == 48: # unit in ft, "0" -> 48
|
elif Mbit == 48: # unit in ft, "0" -> 48
|
||||||
if Qbit == 49: # 25ft interval, "1" -> 49
|
if Qbit == 49: # 25ft interval, "1" -> 49
|
||||||
|
@ -211,7 +211,7 @@ def tell(msg: str) -> None:
|
|||||||
_print("True airspeed", commb.tas50(msg), "knots")
|
_print("True airspeed", commb.tas50(msg), "knots")
|
||||||
|
|
||||||
if BDS == "BDS60":
|
if BDS == "BDS60":
|
||||||
_print("Megnatic Heading", commb.hdg60(msg), "degrees")
|
_print("Magnetic Heading", commb.hdg60(msg), "degrees")
|
||||||
_print("Indicated airspeed", commb.ias60(msg), "knots")
|
_print("Indicated airspeed", commb.ias60(msg), "knots")
|
||||||
_print("Mach number", commb.mach60(msg))
|
_print("Mach number", commb.mach60(msg))
|
||||||
_print("Vertical rate (Baro)", commb.vr60baro(msg), "feet/minute")
|
_print("Vertical rate (Baro)", commb.vr60baro(msg), "feet/minute")
|
||||||
|
@ -12,6 +12,7 @@ The ADS-B module also imports functions from the following modules:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from .. import common
|
from .. import common
|
||||||
@ -161,9 +162,7 @@ def position(
|
|||||||
raise RuntimeError("Incorrect or inconsistent message types")
|
raise RuntimeError("Incorrect or inconsistent message types")
|
||||||
|
|
||||||
|
|
||||||
def position_with_ref(
|
def position_with_ref(msg: str, lat_ref: float, lon_ref: float) -> tuple[float, float]:
|
||||||
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
|
||||||
@ -425,7 +424,7 @@ def nic_v1(msg: str, NICs: int) -> tuple[int, None | float, None | float]:
|
|||||||
return NIC, Rc, VPL
|
return NIC, Rc, VPL
|
||||||
|
|
||||||
|
|
||||||
def nic_v2(msg: str, NICa: int, NICbc: int) -> tuple[int, int]:
|
def nic_v2(msg: str, NICa: int, NICbc: int) -> tuple[int | None, int | None]:
|
||||||
"""Calculate NIC, navigation integrity category, for ADS-B version 2
|
"""Calculate NIC, navigation integrity category, for ADS-B version 2
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -456,10 +455,9 @@ def nic_v2(msg: str, NICa: int, NICbc: int) -> tuple[int, int]:
|
|||||||
try:
|
try:
|
||||||
if isinstance(NIC, dict):
|
if isinstance(NIC, dict):
|
||||||
NIC = NIC[NICs]
|
NIC = NIC[NICs]
|
||||||
|
|
||||||
Rc = uncertainty.NICv2[NIC][NICs]["Rc"]
|
Rc = uncertainty.NICv2[NIC][NICs]["Rc"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
Rc = uncertainty.NA
|
return None, None
|
||||||
|
|
||||||
return NIC, Rc # type: ignore
|
return NIC, Rc # type: ignore
|
||||||
|
|
||||||
|
@ -88,9 +88,9 @@ def capability(msg: str) -> tuple[int, None | str]:
|
|||||||
)
|
)
|
||||||
elif ca == 7:
|
elif ca == 7:
|
||||||
text = (
|
text = (
|
||||||
"Downlink Request value is 0, "
|
"Downlink Request value is not 0, "
|
||||||
"or the Flight Status is 2, 3, 4 or 5, "
|
"or the Flight Status is 2, 3, 4 or 5, "
|
||||||
"either airborne or on the ground"
|
"and either airborne or on the ground"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
text = None
|
text = None
|
||||||
|
@ -82,7 +82,7 @@ def airborne_position(
|
|||||||
if lon > 180:
|
if lon > 180:
|
||||||
lon = lon - 360
|
lon = lon - 360
|
||||||
|
|
||||||
return round(lat, 5), round(lon, 5)
|
return lat, lon
|
||||||
|
|
||||||
|
|
||||||
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 round(lat, 5), round(lon, 5)
|
return lat, lon
|
||||||
|
|
||||||
|
|
||||||
def altitude(msg: str) -> None | int:
|
def altitude(msg: str) -> None | int:
|
||||||
@ -152,6 +152,11 @@ def altitude(msg: str) -> None | int:
|
|||||||
|
|
||||||
if tc < 19:
|
if tc < 19:
|
||||||
altcode = altbin[0:6] + "0" + altbin[6:]
|
altcode = altbin[0:6] + "0" + altbin[6:]
|
||||||
return common.altitude(altcode)
|
alt = common.altitude(altcode)
|
||||||
|
if alt != -999999:
|
||||||
|
return alt
|
||||||
|
else:
|
||||||
|
# return None if altitude is invalid
|
||||||
|
return None
|
||||||
else:
|
else:
|
||||||
return common.bin2int(altbin) * 3.28084 # type: ignore
|
return common.bin2int(altbin) * 3.28084 # type: ignore
|
||||||
|
@ -19,7 +19,6 @@ 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.
|
||||||
|
|
||||||
@ -92,7 +91,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 round(lat, 5), round(lon, 5)
|
return lat, lon
|
||||||
|
|
||||||
|
|
||||||
def surface_position_with_ref(
|
def surface_position_with_ref(
|
||||||
@ -139,12 +138,12 @@ def surface_position_with_ref(
|
|||||||
|
|
||||||
lon = d_lon * (m + cprlon)
|
lon = d_lon * (m + cprlon)
|
||||||
|
|
||||||
return round(lat, 5), round(lon, 5)
|
return lat, lon
|
||||||
|
|
||||||
|
|
||||||
def surface_velocity(
|
def surface_velocity(
|
||||||
msg: str, source: bool = False
|
msg: str, source: bool = False
|
||||||
) -> tuple[None | float, float, int, str]:
|
) -> tuple[None | float, None | float, int, str]:
|
||||||
"""Decode surface velocity from a surface position message
|
"""Decode surface velocity from a surface position message
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -173,7 +172,6 @@ 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,7 +48,6 @@ 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])
|
||||||
|
|
||||||
@ -77,7 +76,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 = round(trk, 2)
|
trk_or_hdg = trk
|
||||||
|
|
||||||
spd_type = "GS"
|
spd_type = "GS"
|
||||||
dir_type = "TRUE_NORTH"
|
dir_type = "TRUE_NORTH"
|
||||||
@ -87,7 +86,6 @@ 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 round(speed, 0), round(direction, 1)
|
return speed, direction
|
||||||
|
|
||||||
|
|
||||||
def temp44(msg: str) -> Tuple[float, float]:
|
def temp44(msg: str) -> Tuple[float, float]:
|
||||||
@ -96,10 +96,8 @@ 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
|
||||||
|
|
||||||
@ -140,7 +138,7 @@ def hum44(msg: str) -> Optional[float]:
|
|||||||
|
|
||||||
hm = common.bin2int(d[50:56]) * 100 / 64 # %
|
hm = common.bin2int(d[50:56]) * 100 / 64 # %
|
||||||
|
|
||||||
return round(hm, 1)
|
return hm
|
||||||
|
|
||||||
|
|
||||||
def turb44(msg: str) -> Optional[int]:
|
def turb44(msg: str) -> Optional[int]:
|
||||||
|
@ -171,7 +171,6 @@ 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 round(angle, 1)
|
return angle
|
||||||
|
|
||||||
|
|
||||||
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 round(trk, 3)
|
return trk
|
||||||
|
|
||||||
|
|
||||||
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 round(angle, 3)
|
return angle
|
||||||
|
|
||||||
|
|
||||||
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 round(hdg, 3)
|
return hdg
|
||||||
|
|
||||||
|
|
||||||
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 round(mach, 3)
|
return mach
|
||||||
|
|
||||||
|
|
||||||
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 round(tas, 1)
|
return tas
|
||||||
|
|
||||||
|
|
||||||
def vr53(msg: str) -> Optional[int]:
|
def vr53(msg: str) -> Optional[int]:
|
||||||
|
@ -200,7 +200,6 @@ 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,4 +1,5 @@
|
|||||||
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
|
||||||
|
|
||||||
@ -10,8 +11,8 @@ class DecodedMessage(TypedDict):
|
|||||||
icao24: str
|
icao24: str
|
||||||
latitude: float
|
latitude: float
|
||||||
longitude: float
|
longitude: float
|
||||||
altitude: int
|
altitude: Annotated[int, "m"]
|
||||||
vertical_speed: float
|
vertical_speed: Annotated[float, "m/s"]
|
||||||
groundspeed: int
|
groundspeed: int
|
||||||
track: int
|
track: int
|
||||||
type: str
|
type: str
|
||||||
|
@ -130,17 +130,18 @@ def flarm(long timestamp, str msg, float refLat, float refLon, **kwargs):
|
|||||||
return dict(
|
return dict(
|
||||||
timestamp=timestamp,
|
timestamp=timestamp,
|
||||||
icao24=icao24,
|
icao24=icao24,
|
||||||
latitude=round(lat * 1e-7, 6),
|
latitude=lat * 1e-7,
|
||||||
longitude=round(lon * 1e-7, 6),
|
longitude=lon * 1e-7,
|
||||||
geoaltitude=altitude,
|
geoaltitude=altitude,
|
||||||
vertical_speed=raw_vs * mult_factor / 10,
|
vertical_speed=raw_vs * mult_factor / 10,
|
||||||
groundspeed=round(speed),
|
groundspeed=speed,
|
||||||
track=round(heading4 - 4 * turningRate(heading4, heading8) / 4),
|
track=heading4 - 4 * turningRate(heading4, heading8) / 4,
|
||||||
type=AIRCRAFT_TYPES[aircraft_type],
|
type=AIRCRAFT_TYPES[aircraft_type],
|
||||||
sensorLatitude=refLat,
|
sensorLatitude=refLat,
|
||||||
sensorLongitude=refLon,
|
sensorLongitude=refLon,
|
||||||
isIcao24=magic==0x10,
|
isIcao24=magic==0x10,
|
||||||
noTrack=noTrack,
|
noTrack=noTrack,
|
||||||
stealth=stealth,
|
stealth=stealth,
|
||||||
|
gps=gps,
|
||||||
**kwargs
|
**kwargs
|
||||||
)
|
)
|
@ -131,7 +131,7 @@ def lockout(msg):
|
|||||||
if uf(msg) in {4, 5, 20, 21}:
|
if uf(msg) in {4, 5, 20, 21}:
|
||||||
lockout = False
|
lockout = False
|
||||||
di = mbytes[1] & 0x7
|
di = mbytes[1] & 0x7
|
||||||
if di == 7:
|
if (di == 1 or di == 7):
|
||||||
# LOS
|
# LOS
|
||||||
if ((mbytes[3] & 0x40) >> 6) == 1:
|
if ((mbytes[3] & 0x40) >> 6) == 1:
|
||||||
lockout = True
|
lockout = True
|
||||||
@ -187,10 +187,16 @@ def uplink_fields(msg):
|
|||||||
if RR > 15:
|
if RR > 15:
|
||||||
BDS1 = RR - 16
|
BDS1 = RR - 16
|
||||||
BDS2 = 0
|
BDS2 = 0
|
||||||
if di == 0 or di == 1:
|
if di == 0:
|
||||||
|
# II
|
||||||
|
II = (mbytes[2] >> 4) & 0xF
|
||||||
|
IC = "II" + str(II)
|
||||||
|
elif di == 1:
|
||||||
# II
|
# II
|
||||||
II = (mbytes[2] >> 4) & 0xF
|
II = (mbytes[2] >> 4) & 0xF
|
||||||
IC = "II" + str(II)
|
IC = "II" + str(II)
|
||||||
|
if ((mbytes[3] & 0x40) >> 6) == 1:
|
||||||
|
lockout = True
|
||||||
elif di == 7:
|
elif di == 7:
|
||||||
# LOS
|
# LOS
|
||||||
if ((mbytes[3] & 0x40) >> 6) == 1:
|
if ((mbytes[3] & 0x40) >> 6) == 1:
|
||||||
|
@ -1,14 +1,22 @@
|
|||||||
|
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:
|
except ImportError:
|
||||||
print("------------------------------------------------------------------------")
|
print(import_msg)
|
||||||
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
|
||||||
@ -24,9 +32,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):
|
def __init__(self, **kwargs) -> None:
|
||||||
super(RtlReader, self).__init__()
|
super(RtlReader, self).__init__()
|
||||||
self.signal_buffer = [] # amplitude of the sample only
|
self.signal_buffer: list[float] = [] # 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
|
||||||
@ -39,7 +47,7 @@ class RtlReader(object):
|
|||||||
|
|
||||||
self.exception_queue = None
|
self.exception_queue = None
|
||||||
|
|
||||||
def _calc_noise(self):
|
def _calc_noise(self) -> float:
|
||||||
"""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)
|
||||||
@ -50,7 +58,7 @@ class RtlReader(object):
|
|||||||
)
|
)
|
||||||
return min(means)
|
return min(means)
|
||||||
|
|
||||||
def _process_buffer(self):
|
def _process_buffer(self) -> list[list[Any]]:
|
||||||
"""process raw IQ data in the buffer"""
|
"""process raw IQ data in the buffer"""
|
||||||
|
|
||||||
# update noise floor
|
# update noise floor
|
||||||
@ -70,17 +78,18 @@ class RtlReader(object):
|
|||||||
i += 1
|
i += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if self._check_preamble(self.signal_buffer[i : i + pbits * 2]):
|
frame_start = i + pbits * 2
|
||||||
frame_start = i + pbits * 2
|
if self._check_preamble(self.signal_buffer[i:frame_start]):
|
||||||
frame_end = i + pbits * 2 + (fbits + 1) * 2
|
|
||||||
frame_length = (fbits + 1) * 2
|
frame_length = (fbits + 1) * 2
|
||||||
|
frame_end = frame_start + frame_length
|
||||||
frame_pulses = self.signal_buffer[frame_start:frame_end]
|
frame_pulses = self.signal_buffer[frame_start:frame_end]
|
||||||
|
|
||||||
threshold = max(frame_pulses) * 0.2
|
threshold = max(frame_pulses) * 0.2
|
||||||
|
|
||||||
msgbin = []
|
msgbin: list[int] = []
|
||||||
for j in range(0, frame_length, 2):
|
for j in range(0, frame_length, 2):
|
||||||
p2 = frame_pulses[j : j + 2]
|
j_2 = j + 2
|
||||||
|
p2 = frame_pulses[j:j_2]
|
||||||
if len(p2) < 2:
|
if len(p2) < 2:
|
||||||
break
|
break
|
||||||
|
|
||||||
@ -117,7 +126,7 @@ class RtlReader(object):
|
|||||||
|
|
||||||
return messages
|
return messages
|
||||||
|
|
||||||
def _check_preamble(self, pulses):
|
def _check_preamble(self, pulses) -> bool:
|
||||||
if len(pulses) != 16:
|
if len(pulses) != 16:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -127,7 +136,7 @@ class RtlReader(object):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _check_msg(self, msg):
|
def _check_msg(self, msg) -> bool:
|
||||||
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:
|
||||||
@ -137,8 +146,9 @@ 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):
|
def _debug_msg(self, msg) -> None:
|
||||||
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:
|
||||||
@ -151,7 +161,7 @@ class RtlReader(object):
|
|||||||
# print("[*]", msg)
|
# print("[*]", msg)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _read_callback(self, data, rtlsdr_obj):
|
def _read_callback(self, data, rtlsdr_obj) -> None:
|
||||||
amp = np.absolute(data)
|
amp = np.absolute(data)
|
||||||
self.signal_buffer.extend(amp.tolist())
|
self.signal_buffer.extend(amp.tolist())
|
||||||
|
|
||||||
@ -159,16 +169,18 @@ class RtlReader(object):
|
|||||||
messages = self._process_buffer()
|
messages = self._process_buffer()
|
||||||
self.handle_messages(messages)
|
self.handle_messages(messages)
|
||||||
|
|
||||||
def handle_messages(self, messages):
|
def handle_messages(self, messages) -> None:
|
||||||
"""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):
|
def stop(self, *args, **kwargs) -> None:
|
||||||
self.sdr.close()
|
self.sdr.close()
|
||||||
|
|
||||||
def run(self, raw_pipe_in=None, stop_flag=None, exception_queue=None):
|
def run(
|
||||||
|
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
|
||||||
|
@ -6,6 +6,7 @@ import time
|
|||||||
import pyModeS as pms
|
import pyModeS as pms
|
||||||
import traceback
|
import traceback
|
||||||
import zmq
|
import zmq
|
||||||
|
import math
|
||||||
|
|
||||||
|
|
||||||
class TcpClient(object):
|
class TcpClient(object):
|
||||||
@ -149,6 +150,103 @@ class TcpClient(object):
|
|||||||
messages.append([msg, ts])
|
messages.append([msg, ts])
|
||||||
return messages
|
return messages
|
||||||
|
|
||||||
|
def read_beast_buffer_rssi_piaware(self):
|
||||||
|
"""Handle mode-s beast data type.
|
||||||
|
|
||||||
|
<esc> "1" : 6 byte MLAT timestamp, 1 byte signal level,
|
||||||
|
2 byte Mode-AC
|
||||||
|
<esc> "2" : 6 byte MLAT timestamp, 1 byte signal level,
|
||||||
|
7 byte Mode-S short frame
|
||||||
|
<esc> "3" : 6 byte MLAT timestamp, 1 byte signal level,
|
||||||
|
14 byte Mode-S long frame
|
||||||
|
<esc> "4" : 6 byte MLAT timestamp, status data, DIP switch
|
||||||
|
configuration settings (not on Mode-S Beast classic)
|
||||||
|
<esc><esc>: true 0x1a
|
||||||
|
<esc> is 0x1a, and "1", "2" and "3" are 0x31, 0x32 and 0x33
|
||||||
|
|
||||||
|
timestamp:
|
||||||
|
wiki.modesbeast.com/Radarcape:Firmware_Versions#The_GPS_timestamp
|
||||||
|
"""
|
||||||
|
messages_mlat = []
|
||||||
|
msg = []
|
||||||
|
i = 0
|
||||||
|
|
||||||
|
# process the buffer until the last divider <esc> 0x1a
|
||||||
|
# then, reset the self.buffer with the remainder
|
||||||
|
|
||||||
|
while i < len(self.buffer):
|
||||||
|
if self.buffer[i : i + 2] == [0x1A, 0x1A]:
|
||||||
|
msg.append(0x1A)
|
||||||
|
i += 1
|
||||||
|
elif (i == len(self.buffer) - 1) and (self.buffer[i] == 0x1A):
|
||||||
|
# special case where the last bit is 0x1a
|
||||||
|
msg.append(0x1A)
|
||||||
|
elif self.buffer[i] == 0x1A:
|
||||||
|
if i == len(self.buffer) - 1:
|
||||||
|
# special case where the last bit is 0x1a
|
||||||
|
msg.append(0x1A)
|
||||||
|
elif len(msg) > 0:
|
||||||
|
messages_mlat.append(msg)
|
||||||
|
msg = []
|
||||||
|
else:
|
||||||
|
msg.append(self.buffer[i])
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
# save the reminder for next reading cycle, if not empty
|
||||||
|
if len(msg) > 0:
|
||||||
|
reminder = []
|
||||||
|
for i, m in enumerate(msg):
|
||||||
|
if (m == 0x1A) and (i < len(msg) - 1):
|
||||||
|
# rewind 0x1a, except when it is at the last bit
|
||||||
|
reminder.extend([m, m])
|
||||||
|
else:
|
||||||
|
reminder.append(m)
|
||||||
|
self.buffer = [0x1A] + msg
|
||||||
|
else:
|
||||||
|
self.buffer = []
|
||||||
|
|
||||||
|
# extract messages
|
||||||
|
messages = []
|
||||||
|
for mm in messages_mlat:
|
||||||
|
ts = time.time()
|
||||||
|
|
||||||
|
msgtype = mm[0]
|
||||||
|
# print(''.join('%02X' % i for i in mm))
|
||||||
|
|
||||||
|
if msgtype == 0x32:
|
||||||
|
# Mode-S Short Message, 7 byte, 14-len hexstr
|
||||||
|
msg = "".join("%02X" % i for i in mm[8:15])
|
||||||
|
elif msgtype == 0x33:
|
||||||
|
# Mode-S Long Message, 14 byte, 28-len hexstr
|
||||||
|
msg = "".join("%02X" % i for i in mm[8:22])
|
||||||
|
else:
|
||||||
|
# Other message tupe
|
||||||
|
continue
|
||||||
|
|
||||||
|
if len(msg) not in [14, 28]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
'''
|
||||||
|
we get the raw 0-255 byte value (raw_rssi = mm[7])
|
||||||
|
we scale it to 0.0 - 1.0 (voltage = raw_rssi / 255)
|
||||||
|
we convert it to a dBFS power value (rolling the squaring of the voltage into the dB calculation)
|
||||||
|
'''
|
||||||
|
|
||||||
|
df = pms.df(msg)
|
||||||
|
raw_rssi = mm[7] # eighth byte of Mode-S message should contain RSSI value
|
||||||
|
rssi_ratio = raw_rssi / 255
|
||||||
|
signalLevel = rssi_ratio ** 2
|
||||||
|
dbfs_rssi = 10 * math.log10(signalLevel)
|
||||||
|
|
||||||
|
# skip incomplete message
|
||||||
|
if df in [0, 4, 5, 11] and len(msg) != 14:
|
||||||
|
continue
|
||||||
|
if df in [16, 17, 18, 19, 20, 21, 24] and len(msg) != 28:
|
||||||
|
continue
|
||||||
|
|
||||||
|
messages.append([msg, dbfs_rssi, ts])
|
||||||
|
return messages
|
||||||
|
|
||||||
def read_skysense_buffer(self):
|
def read_skysense_buffer(self):
|
||||||
"""Skysense stream format.
|
"""Skysense stream format.
|
||||||
|
|
||||||
|
@ -231,7 +231,7 @@ def squawk(binstr: str) -> str:
|
|||||||
binstr (String): 13 bits binary string
|
binstr (String): 13 bits binary string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
int: altitude in ft
|
string: squawk code
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if len(binstr) != 13 or not set(binstr).issubset(set("01")):
|
if len(binstr) != 13 or not set(binstr).issubset(set("01")):
|
||||||
|
@ -73,8 +73,15 @@ class Decode:
|
|||||||
"VFOMr": None,
|
"VFOMr": None,
|
||||||
"PE_RCu": None,
|
"PE_RCu": None,
|
||||||
"PE_VPL": None,
|
"PE_VPL": None,
|
||||||
|
"hum44" : None,
|
||||||
|
"p44" : None,
|
||||||
|
"temp44" : None,
|
||||||
|
"turb44" : None,
|
||||||
|
"wind44" : None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.acs[icao]["tc"] = tc
|
||||||
|
self.acs[icao]["icao"] = icao
|
||||||
self.acs[icao]["t"] = t
|
self.acs[icao]["t"] = t
|
||||||
self.acs[icao]["live"] = int(t)
|
self.acs[icao]["live"] = int(t)
|
||||||
|
|
||||||
@ -155,7 +162,7 @@ class Decode:
|
|||||||
ac["nic_bc"] = pms.adsb.nic_b(msg)
|
ac["nic_bc"] = pms.adsb.nic_b(msg)
|
||||||
|
|
||||||
if (5 <= tc <= 8) or (9 <= tc <= 18) or (20 <= tc <= 22):
|
if (5 <= tc <= 8) or (9 <= tc <= 18) or (20 <= tc <= 22):
|
||||||
ac["HPL"], ac["RCu"], ac["RCv"] = pms.adsb.nuc_p(msg)
|
ac["NUCp"], ac["HPL"], ac["RCu"], ac["RCv"] = pms.adsb.nuc_p(msg)
|
||||||
|
|
||||||
if (ac["ver"] == 1) and ("nic_s" in ac.keys()):
|
if (ac["ver"] == 1) and ("nic_s" in ac.keys()):
|
||||||
ac["Rc"], ac["VPL"] = pms.adsb.nic_v1(msg, ac["nic_s"])
|
ac["Rc"], ac["VPL"] = pms.adsb.nic_v1(msg, ac["nic_s"])
|
||||||
@ -164,20 +171,20 @@ class Decode:
|
|||||||
and ("nic_a" in ac.keys())
|
and ("nic_a" in ac.keys())
|
||||||
and ("nic_bc" in ac.keys())
|
and ("nic_bc" in ac.keys())
|
||||||
):
|
):
|
||||||
ac["Rc"] = pms.adsb.nic_v2(msg, ac["nic_a"], ac["nic_bc"])
|
ac["NIC"], ac["Rc"] = pms.adsb.nic_v2(msg, ac["nic_a"], ac["nic_bc"])
|
||||||
|
|
||||||
if tc == 19:
|
if tc == 19:
|
||||||
ac["HVE"], ac["VVE"] = pms.adsb.nuc_v(msg)
|
ac["NUCv"], ac["HVE"], ac["VVE"] = pms.adsb.nuc_v(msg)
|
||||||
if ac["ver"] in [1, 2]:
|
if ac["ver"] in [1, 2]:
|
||||||
ac["HFOMr"], ac["VFOMr"] = pms.adsb.nac_v(msg)
|
ac["NACv"], ac["HFOMr"], ac["VFOMr"] = pms.adsb.nac_v(msg)
|
||||||
|
|
||||||
if tc == 29:
|
if tc == 29:
|
||||||
ac["PE_RCu"], ac["PE_VPL"], ac["base"] = pms.adsb.sil(msg, ac["ver"])
|
ac["PE_RCu"], ac["PE_VPL"], ac["base"] = pms.adsb.sil(msg, ac["ver"])
|
||||||
ac["EPU"], ac["VEPU"] = pms.adsb.nac_p(msg)
|
ac["NACp"], ac["HEPU"], ac["VEPU"] = pms.adsb.nac_p(msg)
|
||||||
|
|
||||||
if tc == 31:
|
if tc == 31:
|
||||||
ac["ver"] = pms.adsb.version(msg)
|
ac["ver"] = pms.adsb.version(msg)
|
||||||
ac["EPU"], ac["VEPU"] = pms.adsb.nac_p(msg)
|
ac["NACp"], ac["HEPU"], ac["VEPU"] = pms.adsb.nac_p(msg)
|
||||||
ac["PE_RCu"], ac["PE_VPL"], ac["sil_base"] = pms.adsb.sil(
|
ac["PE_RCu"], ac["PE_VPL"], ac["sil_base"] = pms.adsb.sil(
|
||||||
msg, ac["ver"]
|
msg, ac["ver"]
|
||||||
)
|
)
|
||||||
@ -194,6 +201,8 @@ class Decode:
|
|||||||
if icao not in self.acs:
|
if icao not in self.acs:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
self.acs[icao]["icao"] = icao
|
||||||
|
self.acs[icao]["t"] = t
|
||||||
self.acs[icao]["live"] = int(t)
|
self.acs[icao]["live"] = int(t)
|
||||||
|
|
||||||
bds = pms.bds.infer(msg)
|
bds = pms.bds.infer(msg)
|
||||||
@ -217,8 +226,10 @@ class Decode:
|
|||||||
output_buffer.append([t, icao, "rtrk50", rtrk50])
|
output_buffer.append([t, icao, "rtrk50", rtrk50])
|
||||||
|
|
||||||
if trk50:
|
if trk50:
|
||||||
|
self.acs[icao]["trk50"] = trk50
|
||||||
output_buffer.append([t, icao, "trk50", trk50])
|
output_buffer.append([t, icao, "trk50", trk50])
|
||||||
if gs50:
|
if gs50:
|
||||||
|
self.acs[icao]["gs50"] = gs50
|
||||||
output_buffer.append([t, icao, "gs50", gs50])
|
output_buffer.append([t, icao, "gs50", gs50])
|
||||||
|
|
||||||
elif bds == "BDS60":
|
elif bds == "BDS60":
|
||||||
@ -241,10 +252,20 @@ class Decode:
|
|||||||
output_buffer.append([t, icao, "mach60", mach60])
|
output_buffer.append([t, icao, "mach60", mach60])
|
||||||
|
|
||||||
if roc60baro:
|
if roc60baro:
|
||||||
|
self.acs[icao]["roc60baro"] = roc60baro
|
||||||
output_buffer.append([t, icao, "roc60baro", roc60baro])
|
output_buffer.append([t, icao, "roc60baro", roc60baro])
|
||||||
if roc60ins:
|
if roc60ins:
|
||||||
|
self.acs[icao]["roc60ins"] = roc60ins
|
||||||
output_buffer.append([t, icao, "roc60ins", roc60ins])
|
output_buffer.append([t, icao, "roc60ins", roc60ins])
|
||||||
|
|
||||||
|
elif bds == "BDS44":
|
||||||
|
if(pms.commb.is44(msg)):
|
||||||
|
self.acs[icao]["hum44"] = pms.commb.hum44(msg)
|
||||||
|
self.acs[icao]["p44"] = pms.commb.p44(msg)
|
||||||
|
self.acs[icao]["temp44"] = pms.commb.temp44(msg)
|
||||||
|
self.acs[icao]["turb44"] = pms.commb.turb44(msg)
|
||||||
|
self.acs[icao]["wind44"] = pms.commb.wind44(msg)
|
||||||
|
|
||||||
# clear up old data
|
# clear up old data
|
||||||
for icao in list(self.acs.keys()):
|
for icao in list(self.acs.keys()):
|
||||||
if self.t - self.acs[icao]["live"] > self.cache_timeout:
|
if self.t - self.acs[icao]["live"] > self.cache_timeout:
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "pyModeS"
|
name = "pyModeS"
|
||||||
version = "2.11"
|
version = "2.18"
|
||||||
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"
|
||||||
@ -13,48 +13,42 @@ classifiers = [
|
|||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
"Typing :: Typed",
|
"Typing :: Typed",
|
||||||
]
|
]
|
||||||
packages = [
|
packages = [{ include = "pyModeS", from = "." }]
|
||||||
{ include = "pyModeS", from = "." },
|
|
||||||
]
|
|
||||||
include = [
|
include = [
|
||||||
"LICENSE",
|
"LICENSE",
|
||||||
"*.pyx",
|
"*.pyx",
|
||||||
"*.pxd",
|
"*.pxd",
|
||||||
"*.pyi",
|
"*.pyi",
|
||||||
"py.typed",
|
"py.typed",
|
||||||
{ path = "src/pyModeS/**/*.so", format = "wheel" }
|
{ path = "pyModeS/**/*.so", format = "wheel" },
|
||||||
|
{ path = "pyModeS/**/*.pyd", format = "wheel" },
|
||||||
]
|
]
|
||||||
|
build = "build.py"
|
||||||
[tool.poetry.build]
|
|
||||||
generate-setup-file = false
|
|
||||||
script = "build.py"
|
|
||||||
|
|
||||||
[tool.poetry.scripts]
|
[tool.poetry.scripts]
|
||||||
modeslive = "pyModeS.streamer.modeslive:main"
|
modeslive = "pyModeS.streamer.modeslive:main"
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.8"
|
python = ">=3.9"
|
||||||
numpy = "^1.24"
|
numpy = ">=1.26"
|
||||||
pyzmq = "^24.0"
|
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"
|
isort = ">=5.11.4"
|
||||||
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"]
|
||||||
|
|
||||||
[tool.black]
|
[tool.black]
|
||||||
line-length = 80
|
line-length = 80
|
||||||
target_version = ['py38', 'py39', 'py310', 'py311']
|
target_version = ['py39', 'py310', 'py311', 'py312']
|
||||||
include = '\.pyi?$'
|
include = '\.pyi?$'
|
||||||
|
|
||||||
[tool.isort]
|
[tool.isort]
|
||||||
@ -62,5 +56,5 @@ line_length = 80
|
|||||||
profile = "black"
|
profile = "black"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core>=1.0.0", "Cython>=0.29.32"]
|
requires = ["poetry-core>=1.0.0", "Cython>=0.29.32", "setuptools>=69.1.1"]
|
||||||
build-backend = "poetry.core.masonry.api"
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from pyModeS import adsb
|
from pyModeS import adsb
|
||||||
|
from pytest import approx
|
||||||
|
|
||||||
# === TEST ADS-B package ===
|
# === TEST ADS-B package ===
|
||||||
|
|
||||||
@ -22,7 +23,7 @@ def test_adsb_position():
|
|||||||
1446332400,
|
1446332400,
|
||||||
1446332405,
|
1446332405,
|
||||||
)
|
)
|
||||||
assert pos == (49.81755, 6.08442)
|
assert pos == (approx(49.81755, 0.001), approx(6.08442, 0.001))
|
||||||
|
|
||||||
|
|
||||||
def test_adsb_position_swap_odd_even():
|
def test_adsb_position_swap_odd_even():
|
||||||
@ -32,32 +33,32 @@ def test_adsb_position_swap_odd_even():
|
|||||||
1446332405,
|
1446332405,
|
||||||
1446332400,
|
1446332400,
|
||||||
)
|
)
|
||||||
assert pos == (49.81755, 6.08442)
|
assert pos == (approx(49.81755, 0.001), approx(6.08442, 0.001))
|
||||||
|
|
||||||
|
|
||||||
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 == (49.82410, 6.06785)
|
assert pos == (approx(49.82410, 0.001), approx(6.06785, 0.001))
|
||||||
pos = adsb.position_with_ref("8FC8200A3AB8F5F893096B000000", -43.5, 172.5)
|
pos = adsb.position_with_ref("8FC8200A3AB8F5F893096B000000", -43.5, 172.5)
|
||||||
assert pos == (-43.48564, 172.53942)
|
assert pos == (approx(-43.48564, 0.001), approx(172.53942, 0.001))
|
||||||
|
|
||||||
|
|
||||||
def test_adsb_airborne_position_with_ref():
|
def test_adsb_airborne_position_with_ref():
|
||||||
pos = adsb.airborne_position_with_ref(
|
pos = adsb.airborne_position_with_ref(
|
||||||
"8D40058B58C901375147EFD09357", 49.0, 6.0
|
"8D40058B58C901375147EFD09357", 49.0, 6.0
|
||||||
)
|
)
|
||||||
assert pos == (49.82410, 6.06785)
|
assert pos == (approx(49.82410, 0.001), approx(6.06785, 0.001))
|
||||||
pos = adsb.airborne_position_with_ref(
|
pos = adsb.airborne_position_with_ref(
|
||||||
"8D40058B58C904A87F402D3B8C59", 49.0, 6.0
|
"8D40058B58C904A87F402D3B8C59", 49.0, 6.0
|
||||||
)
|
)
|
||||||
assert pos == (49.81755, 6.08442)
|
assert pos == (approx(49.81755, 0.001), approx(6.08442, 0.001))
|
||||||
|
|
||||||
|
|
||||||
def test_adsb_surface_position_with_ref():
|
def test_adsb_surface_position_with_ref():
|
||||||
pos = adsb.surface_position_with_ref(
|
pos = adsb.surface_position_with_ref(
|
||||||
"8FC8200A3AB8F5F893096B000000", -43.5, 172.5
|
"8FC8200A3AB8F5F893096B000000", -43.5, 172.5
|
||||||
)
|
)
|
||||||
assert pos == (-43.48564, 172.53942)
|
assert pos == (approx(-43.48564, 0.001), approx(172.53942, 0.001))
|
||||||
|
|
||||||
|
|
||||||
def test_adsb_surface_position():
|
def test_adsb_surface_position():
|
||||||
@ -69,7 +70,7 @@ def test_adsb_surface_position():
|
|||||||
-43.496,
|
-43.496,
|
||||||
172.558,
|
172.558,
|
||||||
)
|
)
|
||||||
assert pos == (-43.48564, 172.53942)
|
assert pos == (approx(-43.48564, 0.001), approx(172.53942, 0.001))
|
||||||
|
|
||||||
|
|
||||||
def test_adsb_alt():
|
def test_adsb_alt():
|
||||||
@ -80,9 +81,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, 182.88, -832, "GS")
|
assert vgs == (159, approx(182.88, 0.1), -832, "GS")
|
||||||
assert vas == (375, 243.98, -2304, "TAS")
|
assert vas == (375, approx(243.98, 0.1), -2304, "TAS")
|
||||||
assert vgs_surface == (19, 42.2, 0, "GS")
|
assert vgs_surface == (19, approx(42.2, 0.1), 0, "GS")
|
||||||
assert adsb.altitude_diff("8D485020994409940838175B284F") == 550
|
assert adsb.altitude_diff("8D485020994409940838175B284F") == 550
|
||||||
|
|
||||||
|
|
||||||
@ -96,7 +97,9 @@ 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") == 66.8
|
assert adsb.selected_heading("8DA05629EA21485CBF3F8CADAEEB") == approx(
|
||||||
|
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
|
||||||
import pytest
|
from pytest import approx
|
||||||
|
|
||||||
# from pyModeS import ehs, els # deprecated
|
# from pyModeS import ehs, els # deprecated
|
||||||
|
|
||||||
@ -23,32 +23,24 @@ def test_bds40_functions():
|
|||||||
|
|
||||||
|
|
||||||
def test_bds50_functions():
|
def test_bds50_functions():
|
||||||
assert bds.bds50.roll50("A000139381951536E024D4CCF6B5") == 2.1
|
msg1 = "A000139381951536E024D4CCF6B5"
|
||||||
assert bds.bds50.roll50("A0001691FFD263377FFCE02B2BF9") == -0.4
|
msg2 = "A0001691FFD263377FFCE02B2BF9"
|
||||||
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
|
|
||||||
|
|
||||||
assert commb.roll50("A000139381951536E024D4CCF6B5") == 2.1
|
for module in [bds.bds50, commb]:
|
||||||
assert commb.roll50("A0001691FFD263377FFCE02B2BF9") == -0.4 # signed value
|
assert module.roll50(msg1) == approx(2.1, 0.01)
|
||||||
assert commb.trk50("A000139381951536E024D4CCF6B5") == 114.258
|
assert module.roll50(msg2) == approx(-0.35, 0.01) # signed value
|
||||||
assert commb.gs50("A000139381951536E024D4CCF6B5") == 438
|
assert module.trk50(msg1) == approx(114.258, 0.1)
|
||||||
assert commb.rtrk50("A000139381951536E024D4CCF6B5") == 0.125
|
assert module.gs50(msg1) == 438
|
||||||
assert commb.tas50("A000139381951536E024D4CCF6B5") == 424
|
assert module.rtrk50(msg1) == 0.125
|
||||||
|
assert module.tas50(msg1) == 424
|
||||||
|
|
||||||
|
|
||||||
def test_bds60_functions():
|
def test_bds60_functions():
|
||||||
msg = "A00004128F39F91A7E27C46ADC21"
|
msg = "A00004128F39F91A7E27C46ADC21"
|
||||||
|
|
||||||
assert bds.bds60.hdg60(msg) == pytest.approx(42.71484)
|
for module in [bds.bds60, commb]:
|
||||||
assert bds.bds60.ias60(msg) == 252
|
assert bds.bds60.hdg60(msg) == approx(42.71484)
|
||||||
assert bds.bds60.mach60(msg) == 0.42
|
assert bds.bds60.ias60(msg) == 252
|
||||||
assert bds.bds60.vr60baro(msg) == -1920
|
assert bds.bds60.mach60(msg) == 0.42
|
||||||
assert bds.bds60.vr60ins(msg) == -1920
|
assert bds.bds60.vr60baro(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
|
|
||||||
|
Loading…
Reference in New Issue
Block a user