Compare commits

..

No commits in common. "master" and "quantities" have entirely different histories.

16 changed files with 575 additions and 738 deletions

View File

@ -1,16 +0,0 @@
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 }}

67
.github/workflows/pypi-publish.yml vendored Normal file
View File

@ -0,0 +1,67 @@
# This workflows will upload a Python Package using Twine when a release is created
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
name: publish
on:
release:
types: [created]
workflow_dispatch:
env:
POETRY_VERSION: "1.3.1"
jobs:
deploy:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [windows-latest, ubuntu-latest, macos-latest]
python-version: ["3.8", "3.9", "3.10", "3.11"]
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Add poetry to windows path
if: "startsWith(runner.os, 'windows')"
run: |
echo "C:\Users\runneradmin\.local\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
- name: Install and configure Poetry
uses: snok/install-poetry@v1.3.3
with:
version: ${{ env.POETRY_VERSION }}
virtualenvs-create: true
virtualenvs-in-project: true
- name: Display Python version
run: poetry run python -c "import sys; print(sys.version)"
- name: Build packages
run: poetry build
- name: Install dependencies
run: |
poetry run pip install --upgrade pip
poetry run pip install twine
- name: Build and publish
env:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
run: |
poetry run twine upload dist/*.whl
- name: Build and publish (source)
if: ${{ startsWith(runner.os, 'windows') && matrix.python-version == '3.11' }}
env:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
run: |
poetry run twine upload dist/*.tar.gz

View File

@ -6,7 +6,7 @@ on:
workflow_dispatch: workflow_dispatch:
env: env:
POETRY_VERSION: "1.6.1" POETRY_VERSION: "1.3.1"
jobs: jobs:
deploy: deploy:
@ -14,22 +14,22 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest, macos-latest, windows-latest] os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.9", "3.10", "3.11", "3.12"] python-version: ["3.8", "3.9", "3.10", "3.11"]
env: env:
PYTHON_VERSION: ${{ matrix.python-version }} PYTHON_VERSION: ${{ matrix.python-version }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v5 uses: actions/setup-python@v4
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-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@v4 uses: actions/cache@v3
if: ${{ !startsWith(runner.os, 'windows') }} if: ${{ !startsWith(runner.os, 'windows') }}
with: with:
path: | path: |
@ -43,7 +43,7 @@ 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.4.0 uses: snok/install-poetry@v1.3.3
with: with:
version: ${{ env.POETRY_VERSION }} version: ${{ env.POETRY_VERSION }}
virtualenvs-create: true virtualenvs-create: true
@ -65,6 +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
uses: codecov/codecov-action@v4 uses: codecov/codecov-action@v3
with: with:
env_vars: PYTHON_VERSION env_vars: PYTHON_VERSION

View File

@ -1,11 +1,11 @@
import os import os
import shutil import shutil
import sys import sys
from distutils.command import build_ext
from distutils.core import Distribution, Extension
# import pip # 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:
@ -46,7 +46,9 @@ def build() -> None:
compiler_directives={"binding": True, "language_level": 3}, compiler_directives={"binding": True, "language_level": 3},
) )
distribution = Distribution({"name": "extended", "ext_modules": ext_modules}) distribution = Distribution(
{"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)
@ -55,7 +57,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_output_mapping(): for output in cmd.get_outputs():
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

996
poetry.lock generated

File diff suppressed because it is too large Load Diff

3
pyModeS/.gitignore vendored
View File

@ -1,3 +0,0 @@
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 = -999999 alt = -9999
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("Magnetic Heading", commb.hdg60(msg), "degrees") _print("Megnatic 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,7 +12,6 @@ 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
@ -162,7 +161,9 @@ def position(
raise RuntimeError("Incorrect or inconsistent message types") raise RuntimeError("Incorrect or inconsistent message types")
def position_with_ref(msg: str, lat_ref: float, lon_ref: float) -> tuple[float, float]: def position_with_ref(
msg: str, lat_ref: float, lon_ref: float
) -> tuple[float, float]:
"""Decode position with only one message. """Decode position with only one message.
A reference position is required, which can be previously A reference position is required, which can be previously
@ -424,7 +425,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 | None, int | None]: def nic_v2(msg: str, NICa: int, NICbc: int) -> tuple[int, int]:
"""Calculate NIC, navigation integrity category, for ADS-B version 2 """Calculate NIC, navigation integrity category, for ADS-B version 2
Args: Args:
@ -455,9 +456,10 @@ def nic_v2(msg: str, NICa: int, NICbc: int) -> tuple[int | None, int | None]:
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:
return None, None Rc = uncertainty.NA
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 not 0, " "Downlink Request value is 0, "
"or the Flight Status is 2, 3, 4 or 5, " "or the Flight Status is 2, 3, 4 or 5, "
"and either airborne or on the ground" "either airborne or on the ground"
) )
else: else:
text = None text = None

View File

@ -152,11 +152,6 @@ 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:]
alt = common.altitude(altcode) return 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

@ -142,6 +142,5 @@ def flarm(long timestamp, str msg, float refLat, float refLon, **kwargs):
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 == 1 or di == 7): if di == 7:
# LOS # LOS
if ((mbytes[3] & 0x40) >> 6) == 1: if ((mbytes[3] & 0x40) >> 6) == 1:
lockout = True lockout = True
@ -187,16 +187,10 @@ def uplink_fields(msg):
if RR > 15: if RR > 15:
BDS1 = RR - 16 BDS1 = RR - 16
BDS2 = 0 BDS2 = 0
if di == 0: if di == 0 or di == 1:
# II # II
II = (mbytes[2] >> 4) & 0xF II = (mbytes[2] >> 4) & 0xF
IC = "II" + str(II) IC = "II" + str(II)
elif di == 1:
# II
II = (mbytes[2] >> 4) & 0xF
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

@ -6,7 +6,6 @@ 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):
@ -150,103 +149,6 @@ 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

@ -73,15 +73,8 @@ 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)
@ -162,7 +155,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["NUCp"], ac["HPL"], ac["RCu"], ac["RCv"] = pms.adsb.nuc_p(msg) 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"])
@ -171,20 +164,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["NIC"], ac["Rc"] = pms.adsb.nic_v2(msg, ac["nic_a"], ac["nic_bc"]) ac["Rc"] = pms.adsb.nic_v2(msg, ac["nic_a"], ac["nic_bc"])
if tc == 19: if tc == 19:
ac["NUCv"], ac["HVE"], ac["VVE"] = pms.adsb.nuc_v(msg) ac["HVE"], ac["VVE"] = pms.adsb.nuc_v(msg)
if ac["ver"] in [1, 2]: if ac["ver"] in [1, 2]:
ac["NACv"], ac["HFOMr"], ac["VFOMr"] = pms.adsb.nac_v(msg) 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["NACp"], ac["HEPU"], ac["VEPU"] = pms.adsb.nac_p(msg) ac["EPU"], 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["NACp"], ac["HEPU"], ac["VEPU"] = pms.adsb.nac_p(msg) ac["EPU"], 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"]
) )
@ -201,8 +194,6 @@ 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)
@ -226,10 +217,8 @@ 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":
@ -252,20 +241,10 @@ 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.18" version = "2.16"
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,42 +13,46 @@ classifiers = [
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Typing :: Typed", "Typing :: Typed",
] ]
packages = [{ include = "pyModeS", from = "." }] packages = [
{ include = "pyModeS", from = "." },
]
include = [ include = [
"LICENSE", "LICENSE",
"*.pyx", "*.pyx",
"*.pxd", "*.pxd",
"*.pyi", "*.pyi",
"py.typed", "py.typed",
{ path = "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.9" python = "^3.8"
numpy = ">=1.26" numpy = "^1.24"
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]
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"
[tool.poetry.extras] [tool.poetry.extras]
rtlsdr = ["pyrtlsdr"] rtlsdr = ["pyrtlsdr"]
[tool.black] [tool.black]
line-length = 80 line-length = 80
target_version = ['py39', 'py310', 'py311', 'py312'] target_version = ['py38', 'py39', 'py310', 'py311']
include = '\.pyi?$' include = '\.pyi?$'
[tool.isort] [tool.isort]
@ -56,5 +60,5 @@ line_length = 80
profile = "black" profile = "black"
[build-system] [build-system]
requires = ["poetry-core>=1.0.0", "Cython>=0.29.32", "setuptools>=69.1.1"] requires = ["poetry-core>=1.0.0", "Cython>=0.29.32"]
build-backend = "poetry.core.masonry.api" build-backend = "poetry.core.masonry.api"