Compare commits

...

53 Commits

Author SHA1 Message Date
Junzi Sun
b4be55116c git workflow 2024-08-25 13:38:06 +02:00
Junzi Sun
fdfa2b3fab fix typing 2024-08-25 12:45:24 +02:00
Junzi Sun
2f5dbdf08d update version and dependencies 2024-08-25 12:37:50 +02:00
Junzi Sun
b22734ad51 fix typing 2024-08-25 12:37:37 +02:00
Junzi Sun
e2de7bc9ac fix typing 2024-08-25 12:33:28 +02:00
gituser283
74ea61fb11
fix decoder and uncertainty issues (#146)
* Created branch mods20230421a

* nic_v2 NIC return is set to None when a KeyError exception is raised

* process_raw receives additional return values from nuc_p, nic_v2, nuc_v, nac_v, nac_p .  hum44, p44, temp44, turb44, wind44, tk50, gs50, roc60baro, roc60ins, tc, and icao added to acs dictionary.

---------

Co-authored-by: Mike M <>
2024-08-25 12:27:12 +02:00
Junzi Sun
08218f558d
Merge pull request #151 from junzis/quantities
Some annotations for physical quantities
2024-08-25 12:23:36 +02:00
Junzi Sun
8acbdf186c
Merge pull request #170 from chrislanzara/master
Fixed "Megnatic" typo
2024-08-25 12:23:03 +02:00
dependabot[bot]
d8dd978730
Bump snok/install-poetry from 1.3.4 to 1.4.0 (#171)
Bumps [snok/install-poetry](https://github.com/snok/install-poetry) from 1.3.4 to 1.4.0.
- [Release notes](https://github.com/snok/install-poetry/releases)
- [Commits](https://github.com/snok/install-poetry/compare/v1.3.4...v1.4.0)

---
updated-dependencies:
- dependency-name: snok/install-poetry
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-25 12:22:23 +02:00
kirth123
01ceb8114a
deleted all the previous commits that were causing problems and added a new commit with better comments (#172) 2024-08-25 12:20:51 +02:00
Chris
4a7c719978
Fixed "Magnetic" typo 2024-07-03 22:49:24 +01:00
Xavier Olive
93794b64ca fix build for py312 2024-03-03 17:22:16 +01:00
Xavier Olive
9646c2fbea bump version 2024-02-02 09:51:44 +01:00
dependabot[bot]
97a50a5b13
Bump actions/cache from 3 to 4 (#166)
Bumps [actions/cache](https://github.com/actions/cache) from 3 to 4.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-01 20:04:17 +01:00
dependabot[bot]
81b54accb5
Bump codecov/codecov-action from 3 to 4 (#165)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3 to 4.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-01 20:03:43 +01:00
dependabot[bot]
942c68dc78
Bump actions/setup-python from 4 to 5 (#163) 2024-01-01 16:29:45 +01:00
Xavier Olive
36fb0de92c upgrade poetry version 2023-12-19 09:59:35 +01:00
Flyer350
582a0d2188
DI = 1 correction for uplink messages (#157)
* Update uplink.py

Adding lockout for DI = 1

* Update uplink.py
2023-12-18 23:03:34 +01:00
Junzi Sun
45ae55d965
Merge pull request #161 from Flyer350/patch-11
Update allcall.py
2023-12-18 23:02:16 +01:00
Junzi Sun
4cf024f144
Merge pull request #162 from paulmadejong/altitude_none_return
Properly return None if altitude could not be decoded
2023-12-18 23:00:38 +01:00
Paul de Jong
eea09227b0 c_common.py: return -999999 (extremely low, physically impossible) altitude in case altitude is unknown or invalid, bds04.py: convert -999999 altitude to None to better signal an invalid/unknown decoded altitude to the user 2023-12-18 16:40:44 +01:00
Flyer350
852315ed2d
Update allcall.py
Modified a mistake in the interpretation of CA field 7.
See ICAO Annex 10, Vol IV., 3.1.2.5.2.2.1
2023-12-04 14:33:36 +01:00
dependabot[bot]
77273153cb
Bump actions/checkout from 3 to 4 (#158)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-01 18:36:54 +02:00
dependabot[bot]
b4c81db151
Bump snok/install-poetry from 1.3.3 to 1.3.4 (#156)
Bumps [snok/install-poetry](https://github.com/snok/install-poetry) from 1.3.3 to 1.3.4.
- [Release notes](https://github.com/snok/install-poetry/releases)
- [Commits](https://github.com/snok/install-poetry/compare/v1.3.3...v1.3.4)

---
updated-dependencies:
- dependency-name: snok/install-poetry
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-02 13:45:09 +09:00
Xavier Olive
b6608b2f78 loosen dependency requirements 2023-08-22 22:38:50 +02:00
Xavier Olive
070dc80bf4 include pyd files to wheel 2023-08-13 10:57:20 +02:00
Xavier Olive
8d9a8df9d9 minor fix 2023-07-18 13:47:06 +00:00
Xavier Olive
458a78c30b add the decoding of the gps status indicator 2023-07-18 13:42:16 +00:00
Junzi Sun
57aa7a7d9c update test 2023-07-18 13:41:54 +00:00
Xavier Olive
b1f8f6ed62
Some annotations for quantities 2023-06-14 22:50:33 +02:00
Junzi Sun
8e2051af68 update test 2023-05-22 17:05:43 +02:00
Paul de Jong
50864b56aa
Remove rounding in pyModeS (#147)
* Update bds50.py: round roll to 3 places instead of 1

Provide a bit more detail for smaller roll angles.

* remove rounding

* update poetry

* update poetry

* update test

* update bds06

* update workflow

---------

Co-authored-by: Junzi Sun <junzisun@gmail.com>
2023-05-22 17:00:58 +02:00
dependabot[bot]
8403cb61e4
Bump actions/checkout from 2 to 3 (#145)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-02 14:45:28 +01:00
dependabot[bot]
a5b8d670d3
Bump actions/setup-python from 2 to 4 (#144)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-02 14:45:05 +01:00
Xavier Olive
4e19ecdfa9 minor changes, new release 2023-02-14 18:39:52 +01:00
Nicholas (Nick) Kruzan
0d7a310007
Update py_common.py (#143) 2023-02-12 13:20:42 +01:00
Xavier Olive
66c29840b0 fix build issues 2023-01-19 15:18:16 +01:00
Xavier Olive
99dc2a4f40 limit wheels to windows, otherwise build from src 2022-12-31 00:42:50 +01:00
Xavier Olive
39936e4472 fix publish 2022-12-30 19:53:33 +01:00
Xavier Olive
144ee1710d fix publish 2022-12-30 19:46:15 +01:00
Xavier Olive
09ed997f91 fix publish 2022-12-30 19:40:25 +01:00
Xavier Olive
e437931254 fix publish 2022-12-30 19:36:16 +01:00
Xavier Olive
72fcc794e7 clean dependencies 2022-12-30 19:22:10 +01:00
Xavier Olive
ff12d819da fix publish source 2022-12-30 14:17:36 +01:00
Xavier Olive
dc9d7ea91a fix test runner.os 2022-12-30 14:10:22 +01:00
Xavier Olive
5bc11cd78c fix test runner.os 2022-12-30 14:08:44 +01:00
Xavier Olive
9e6f9c3ffe upload source 2022-12-30 14:05:38 +01:00
dependabot[bot]
e1017bc2e5
Bump codecov/codecov-action from 2 to 3 (#136)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 2 to 3.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-30 14:02:05 +01:00
Xavier Olive
ebe1f76cd9 dependabot.yml 2022-12-30 13:57:57 +01:00
Xavier Olive
8830ef3dfe fix poetry PATH for windows 2022-12-30 13:50:43 +01:00
Xavier Olive
d999851da5 fix typo and && 2022-12-30 13:42:52 +01:00
Xavier Olive
6d0406c6b9 update version 2022-12-30 13:28:36 +01:00
Xavier Olive
6140f84058 update yml 2022-12-30 00:59:30 +01:00
29 changed files with 863 additions and 1235 deletions

11
.github/dependabot.yml vendored Normal file
View 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
View 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 }}

View File

@ -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/*

View File

@ -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

View File

@ -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

File diff suppressed because it is too large Load Diff

3
pyModeS/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
decoder/flarm/decode.c
extra/demod2400/core.c
c_common.c

View File

@ -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

View File

@ -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")

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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]:

View File

@ -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

View File

@ -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]:

View File

@ -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]:

View File

@ -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

View File

@ -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

View File

@ -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
) )

View File

@ -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:

View File

@ -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

View File

@ -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.

View File

@ -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")):

View File

@ -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:

View File

@ -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"

View File

@ -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

View File

@ -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