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:
env:
POETRY_VERSION: "1.6.1"
POETRY_VERSION: "1.3.1"
jobs:
deploy:
@ -14,22 +14,22 @@ jobs:
strategy:
matrix:
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:
PYTHON_VERSION: ${{ matrix.python-version }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v5
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
# virtualenv cache should depends on OS, Python version and `poetry.lock` (and optionally workflow files).
- name: Cache Packages
uses: actions/cache@v4
uses: actions/cache@v3
if: ${{ !startsWith(runner.os, 'windows') }}
with:
path: |
@ -43,7 +43,7 @@ jobs:
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.4.0
uses: snok/install-poetry@v1.3.3
with:
version: ${{ env.POETRY_VERSION }}
virtualenvs-create: true
@ -65,6 +65,6 @@ jobs:
poetry run pytest tests --cov --cov-report term-missing
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
uses: codecov/codecov-action@v3
with:
env_vars: PYTHON_VERSION

View File

@ -1,11 +1,11 @@
import os
import shutil
import sys
from distutils.command import build_ext
from distutils.core import Distribution, Extension
# import pip
from Cython.Build import cythonize
from setuptools import Distribution, Extension
from setuptools.command import build_ext
def build() -> None:
@ -46,7 +46,9 @@ def build() -> None:
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
cmd = build_ext.build_ext(distribution)
@ -55,7 +57,7 @@ def build() -> None:
cmd.run()
# 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)
shutil.copyfile(output, relative_extension)
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:
# altitude unknown or invalid
alt = -999999
alt = -9999
elif Mbit == 48: # unit in ft, "0" -> 48
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")
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("Mach number", commb.mach60(msg))
_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 datetime import datetime
from .. import common
@ -162,7 +161,9 @@ def position(
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.
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
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
Args:
@ -455,9 +456,10 @@ def nic_v2(msg: str, NICa: int, NICbc: int) -> tuple[int | None, int | None]:
try:
if isinstance(NIC, dict):
NIC = NIC[NICs]
Rc = uncertainty.NICv2[NIC][NICs]["Rc"]
except KeyError:
return None, None
Rc = uncertainty.NA
return NIC, Rc # type: ignore

View File

@ -88,9 +88,9 @@ def capability(msg: str) -> tuple[int, None | str]:
)
elif ca == 7:
text = (
"Downlink Request value is not 0, "
"Downlink Request value is 0, "
"or the Flight Status is 2, 3, 4 or 5, "
"and either airborne or on the ground"
"either airborne or on the ground"
)
else:
text = None

View File

@ -152,11 +152,6 @@ def altitude(msg: str) -> None | int:
if tc < 19:
altcode = altbin[0:6] + "0" + altbin[6:]
alt = common.altitude(altcode)
if alt != -999999:
return alt
else:
# return None if altitude is invalid
return None
return common.altitude(altcode)
else:
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,
noTrack=noTrack,
stealth=stealth,
gps=gps,
**kwargs
)

View File

@ -131,7 +131,7 @@ def lockout(msg):
if uf(msg) in {4, 5, 20, 21}:
lockout = False
di = mbytes[1] & 0x7
if (di == 1 or di == 7):
if di == 7:
# LOS
if ((mbytes[3] & 0x40) >> 6) == 1:
lockout = True
@ -187,16 +187,10 @@ def uplink_fields(msg):
if RR > 15:
BDS1 = RR - 16
BDS2 = 0
if di == 0:
if di == 0 or di == 1:
# II
II = (mbytes[2] >> 4) & 0xF
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:
# LOS
if ((mbytes[3] & 0x40) >> 6) == 1:

View File

@ -6,7 +6,6 @@ import time
import pyModeS as pms
import traceback
import zmq
import math
class TcpClient(object):
@ -150,103 +149,6 @@ class TcpClient(object):
messages.append([msg, ts])
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):
"""Skysense stream format.

View File

@ -73,15 +73,8 @@ class Decode:
"VFOMr": None,
"PE_RCu": 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]["live"] = int(t)
@ -162,7 +155,7 @@ class Decode:
ac["nic_bc"] = pms.adsb.nic_b(msg)
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()):
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_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:
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]:
ac["NACv"], ac["HFOMr"], ac["VFOMr"] = pms.adsb.nac_v(msg)
ac["HFOMr"], ac["VFOMr"] = pms.adsb.nac_v(msg)
if tc == 29:
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:
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(
msg, ac["ver"]
)
@ -201,8 +194,6 @@ class Decode:
if icao not in self.acs:
continue
self.acs[icao]["icao"] = icao
self.acs[icao]["t"] = t
self.acs[icao]["live"] = int(t)
bds = pms.bds.infer(msg)
@ -226,10 +217,8 @@ class Decode:
output_buffer.append([t, icao, "rtrk50", rtrk50])
if trk50:
self.acs[icao]["trk50"] = trk50
output_buffer.append([t, icao, "trk50", trk50])
if gs50:
self.acs[icao]["gs50"] = gs50
output_buffer.append([t, icao, "gs50", gs50])
elif bds == "BDS60":
@ -252,20 +241,10 @@ class Decode:
output_buffer.append([t, icao, "mach60", mach60])
if roc60baro:
self.acs[icao]["roc60baro"] = roc60baro
output_buffer.append([t, icao, "roc60baro", roc60baro])
if roc60ins:
self.acs[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
for icao in list(self.acs.keys()):
if self.t - self.acs[icao]["live"] > self.cache_timeout:

View File

@ -1,6 +1,6 @@
[tool.poetry]
name = "pyModeS"
version = "2.18"
version = "2.16"
description = "Python Mode-S and ADS-B Decoder"
authors = ["Junzi Sun <j.sun-1@tudelft.nl>"]
license = "GNU GPL v3"
@ -13,42 +13,46 @@ classifiers = [
"Programming Language :: Python :: 3",
"Typing :: Typed",
]
packages = [{ include = "pyModeS", from = "." }]
packages = [
{ include = "pyModeS", from = "." },
]
include = [
"LICENSE",
"*.pyx",
"*.pxd",
"*.pyi",
"py.typed",
{ path = "pyModeS/**/*.so", format = "wheel" },
{ path = "pyModeS/**/*.pyd", format = "wheel" },
{ path = "pyModeS/**/*.so", format = "wheel" }
]
build = "build.py"
[tool.poetry.build]
generate-setup-file = false
script = "build.py"
[tool.poetry.scripts]
modeslive = "pyModeS.streamer.modeslive:main"
[tool.poetry.dependencies]
python = ">=3.9"
numpy = ">=1.26"
pyzmq = ">=24.0"
pyrtlsdr = { version = ">=0.2.93", optional = true }
python = "^3.8"
numpy = "^1.24"
pyzmq = "^24.0"
pyrtlsdr = {version = "^0.2.93", optional = true}
[tool.poetry.group.dev.dependencies]
mypy = ">=0.991"
flake8 = ">=5.0.0"
black = ">=22.12.0"
isort = ">=5.11.4"
pytest = ">=7.2.0"
pytest-cov = ">=4.0.0"
codecov = ">=2.1.12"
mypy = "^0.991"
flake8 = "^5.0.0"
black = "^22.12.0"
isort = "^5.11.4"
pytest = "^7.2.0"
pytest-cov = "^4.0.0"
codecov = "^2.1.12"
[tool.poetry.extras]
rtlsdr = ["pyrtlsdr"]
[tool.black]
line-length = 80
target_version = ['py39', 'py310', 'py311', 'py312']
target_version = ['py38', 'py39', 'py310', 'py311']
include = '\.pyi?$'
[tool.isort]
@ -56,5 +60,5 @@ line_length = 80
profile = "black"
[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"