Compare commits

...

17 Commits

Author SHA1 Message Date
Junzi Sun
555b2eea40 reduce complexity and change default type to str 2020-02-26 00:09:52 +01:00
Junzi Sun
dea7cde317 update clean script 2019-10-29 23:59:15 +01:00
Junzi Sun
e16d34bc06 consolidation 2019-10-29 23:55:25 +01:00
Junzi Sun
9bb87b00be update benchmark 2019-10-29 23:39:57 +01:00
Junzi Sun
3dae0438bf update tests 2019-10-29 23:19:37 +01:00
Junzi Sun
02c5117de5 minor updates to C code 2019-10-29 22:33:48 +01:00
Junzi Sun
3f24f78d3a fix hidden altitude() call 2019-10-29 22:24:59 +01:00
Junzi Sun
cf3828d2a0 add make options 2019-10-29 22:24:27 +01:00
Junzi Sun
f70d1f2f1f clean up useless stuff 2019-10-29 18:03:23 +01:00
Junzi Sun
dfeb65fbd7 "make" things easier 2019-10-29 17:53:39 +01:00
Xavier Olive
13b283666a optimisations in bds09 2019-10-29 17:37:45 +01:00
Xavier Olive
6144b88188 bds09 2019-10-29 17:33:32 +01:00
Xavier Olive
b503beb3fd bds08 2019-10-29 16:53:39 +01:00
Xavier Olive
c804cd876c separate cleanly cython and python, bds05, bds06, modulo issues 2019-10-29 16:37:44 +01:00
Xavier Olive
d48caed7e6 add bds05 2019-10-29 11:41:00 +01:00
Xavier Olive
eb675d5ca3 cythonize common 2019-10-29 10:00:01 +01:00
Junzi Sun
b04a1bd49c remove unused functions 2019-10-28 16:24:15 +01:00
31 changed files with 751 additions and 371 deletions

3
.gitignore vendored
View File

@ -5,6 +5,9 @@ __pycache__/
*.py[cod] *.py[cod]
.pytest_cache/ .pytest_cache/
#cython
.c
# C extensions # C extensions
*.so *.so

19
Makefile Normal file
View File

@ -0,0 +1,19 @@
install:
pip install . --upgrade
uninstall:
pip uninstall pyModeS -y
ext:
python setup.py build_ext --inplace
test:
python -m pytest
clean:
find pyModeS/decoder -type f -name '*.c' -delete
find pyModeS/decoder -type f -name '*.so' -delete
find . | grep -E "(__pycache__|\.pyc|\.pyo$$)" | xargs rm -rf
rm -rf *.egg-info
rm -rf .pytest_cache
rm -rf build/*

View File

@ -321,8 +321,19 @@ Here is an example:
Unit test Unit test
--------- ---------
To perform unit tests. First, install ``tox`` through pip. Then, run the following commands: To perform unit tests, ``pytest`` must be install first.
.. code:: bash Build Cython extensions
::
$ tox $ make ext
Run unit tests
::
$ make test
Clean build files
::
$ make clean

View File

@ -3,11 +3,16 @@ from __future__ import absolute_import, print_function, division
import os import os
import warnings import warnings
from .decoder.common import * try:
from .decoder import c_common as common
from .decoder.c_common import *
except:
from .decoder import common
from .decoder.common import *
from .decoder import tell from .decoder import tell
from .decoder import adsb from .decoder import adsb
from .decoder import commb from .decoder import commb
from .decoder import common
from .decoder import bds from .decoder import bds
from .extra import aero from .extra import aero
from .extra import tcpclient from .extra import tcpclient

View File

@ -1,18 +1,3 @@
# Copyright (C) 2018 Junzi Sun (TU Delft)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
""" """
Decoding Air-Air Surveillance (ACAS) DF=0/16 Decoding Air-Air Surveillance (ACAS) DF=0/16

View File

@ -1,18 +1,3 @@
# Copyright (C) 2015 Junzi Sun (TU Delft)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""ADS-B Wrapper. """ADS-B Wrapper.
The ADS-B wrapper also imports functions from the following modules: The ADS-B wrapper also imports functions from the following modules:
@ -38,7 +23,7 @@ from pyModeS.decoder import uncertainty
from pyModeS.decoder.bds.bds05 import ( from pyModeS.decoder.bds.bds05 import (
airborne_position, airborne_position,
airborne_position_with_ref, airborne_position_with_ref,
altitude, altitude as altitude05,
) )
from pyModeS.decoder.bds.bds06 import ( from pyModeS.decoder.bds.bds06 import (
surface_position, surface_position,
@ -143,18 +128,13 @@ def altitude(msg):
if tc < 5 or tc == 19 or tc > 22: if tc < 5 or tc == 19 or tc > 22:
raise RuntimeError("%s: Not a position message" % msg) raise RuntimeError("%s: Not a position message" % msg)
if tc >= 5 and tc <= 8: elif tc >= 5 and tc <= 8:
# surface position, altitude 0 # surface position, altitude 0
return 0 return 0
msgbin = common.hex2bin(msg)
q = msgbin[47]
if q:
n = common.bin2int(msgbin[40:47] + msgbin[48:52])
alt = n * 25 - 1000
return alt
else: else:
return None # airborn position
return altitude05(msg)
def velocity(msg, rtn_sources=False): def velocity(msg, rtn_sources=False):
@ -177,7 +157,7 @@ def velocity(msg, rtn_sources=False):
as refrence, 'mag_north' for magnetic north as reference), as refrence, 'mag_north' for magnetic north as reference),
rate of climb/descent source ('Baro' for barometer, 'GNSS' rate of climb/descent source ('Baro' for barometer, 'GNSS'
for GNSS constellation). for GNSS constellation).
In the case of surface messages, None will be put in place In the case of surface messages, None will be put in place
for vertical rate and its respective sources. for vertical rate and its respective sources.
""" """

View File

@ -1,18 +1,3 @@
# Copyright (C) 2018 Junzi Sun (TU Delft)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
""" """
Decoding all call replies DF=11 Decoding all call replies DF=11

View File

@ -1,19 +1,3 @@
# Copyright (C) 2018 Junzi Sun (TU Delft)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# ------------------------------------------ # ------------------------------------------
# BDS 0,5 # BDS 0,5
# ADS-B TC=9-18 # ADS-B TC=9-18

View File

@ -1,19 +1,3 @@
# Copyright (C) 2018 Junzi Sun (TU Delft)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# ------------------------------------------ # ------------------------------------------
# BDS 0,6 # BDS 0,6
# ADS-B TC=5-8 # ADS-B TC=5-8

View File

@ -1,19 +1,3 @@
# Copyright (C) 2018 Junzi Sun (TU Delft)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# ------------------------------------------ # ------------------------------------------
# BDS 0,8 # BDS 0,8
# ADS-B TC=1-4 # ADS-B TC=1-4

View File

@ -1,19 +1,3 @@
# Copyright (C) 2018 Junzi Sun (TU Delft)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# ------------------------------------------ # ------------------------------------------
# BDS 0,9 # BDS 0,9
# ADS-B TC=19 # ADS-B TC=19

View File

@ -1,18 +1,3 @@
# Copyright (C) 2018 Junzi Sun (TU Delft)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# ------------------------------------------ # ------------------------------------------
# BDS 1,0 # BDS 1,0
# Data link capability report # Data link capability report

View File

@ -1,19 +1,3 @@
# Copyright (C) 2018 Junzi Sun (TU Delft)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# ------------------------------------------ # ------------------------------------------
# BDS 1,7 # BDS 1,7
# Common usage GICB capability report # Common usage GICB capability report

View File

@ -1,18 +1,3 @@
# Copyright (C) 2018 Junzi Sun (TU Delft)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# ------------------------------------------ # ------------------------------------------
# BDS 2,0 # BDS 2,0
# Aircraft identification # Aircraft identification

View File

@ -1,18 +1,3 @@
# Copyright (C) 2018 Junzi Sun (TU Delft)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# ------------------------------------------ # ------------------------------------------
# BDS 3,0 # BDS 3,0
# ACAS active resolution advisory # ACAS active resolution advisory

View File

@ -1,18 +1,3 @@
# Copyright (C) 2018 Junzi Sun (TU Delft)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# ------------------------------------------ # ------------------------------------------
# BDS 4,0 # BDS 4,0
# Selected vertical intention # Selected vertical intention

View File

@ -1,18 +1,3 @@
# Copyright (C) 2018 Junzi Sun (TU Delft)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# ------------------------------------------ # ------------------------------------------
# BDS 4,4 # BDS 4,4
# Meteorological routine air report # Meteorological routine air report

View File

@ -1,18 +1,3 @@
# Copyright (C) 2018 Junzi Sun (TU Delft)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# ------------------------------------------ # ------------------------------------------
# BDS 4,5 # BDS 4,5
# Meteorological hazard report # Meteorological hazard report

View File

@ -1,18 +1,3 @@
# Copyright (C) 2018 Junzi Sun (TU Delft)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# ------------------------------------------ # ------------------------------------------
# BDS 5,0 # BDS 5,0
# Track and turn report # Track and turn report

View File

@ -1,18 +1,3 @@
# Copyright (C) 2018 Junzi Sun (TU Delft)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# ------------------------------------------ # ------------------------------------------
# BDS 5,3 # BDS 5,3
# Air-referenced state vector # Air-referenced state vector

View File

@ -1,18 +1,3 @@
# Copyright (C) 2018 Junzi Sun (TU Delft)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# ------------------------------------------ # ------------------------------------------
# BDS 6,0 # BDS 6,0
# Heading and speed report # Heading and speed report

View File

@ -0,0 +1,23 @@
# cython: language_level=3
cdef int char_to_int(unsigned char binstr)
cdef unsigned char int_to_char(unsigned char i)
cpdef str hex2bin(str hexstr)
cpdef long bin2int(str binstr)
cpdef long hex2int(str binstr)
cpdef unsigned char df(str msg)
cpdef long crc(str msg, bint encode=*)
cpdef long floor(double x)
cpdef str icao(str msg)
cpdef bint is_icao_assigned(str icao)
cpdef int typecode(str msg)
cpdef int cprNL(double lat)
cpdef str idcode(str msg)
cpdef int altcode(str msg)
cdef str data(str msg)
cpdef bint allzeros(str msg)

View File

@ -0,0 +1,431 @@
# cython: language_level=3
cimport cython
from cpython cimport array
from cpython.bytes cimport PyBytes_GET_SIZE
from cpython.bytearray cimport PyByteArray_GET_SIZE
from libc.math cimport cos, acos, fabs, M_PI as pi, floor as c_floor
cdef int char_to_int(unsigned char binstr):
if 48 <= binstr <= 57: # 0 to 9
return binstr - 48
if 97 <= binstr <= 102: # a to f
return binstr - 97 + 10
if 65 <= binstr <= 70: # A to F
return binstr - 65 + 10
return 0
cdef unsigned char int_to_char(unsigned char i):
if i < 10:
return 48 + i # "0" + i
return 97 - 10 + i # "a" - 10 + i
@cython.boundscheck(False)
@cython.overflowcheck(False)
cpdef str hex2bin(str hexstr):
"""Convert a hexdecimal string to binary string, with zero fillings."""
# num_of_bits = len(hexstr) * 4
cdef hexbytes = bytes(hexstr.encode())
cdef Py_ssize_t len_hexstr = PyBytes_GET_SIZE(hexbytes)
# binstr = bin(int(hexbytes, 16))[2:].zfill(int(num_of_bits))
cdef bytearray _binstr = bytearray(4 * len_hexstr)
cdef unsigned char[:] binstr = _binstr
cdef unsigned char int_
cdef Py_ssize_t i
for i in range(len_hexstr):
int_ = char_to_int(hexbytes[i])
binstr[4*i] = int_to_char((int_ >> 3) & 1)
binstr[4*i+1] = int_to_char((int_ >> 2) & 1)
binstr[4*i+2] = int_to_char((int_ >> 1) & 1)
binstr[4*i+3] = int_to_char((int_) & 1)
return _binstr.decode()
@cython.boundscheck(False)
cpdef long bin2int(str binstr):
"""Convert a binary string to integer."""
# return int(binstr, 2)
cdef bytearray binbytes = bytearray(binstr.encode())
cdef Py_ssize_t len_ = PyByteArray_GET_SIZE(binbytes)
cdef long cumul = 0
cdef unsigned char[:] v_binstr = binbytes
for i in range(len_):
cumul = 2*cumul + char_to_int(v_binstr[i])
return cumul
@cython.boundscheck(False)
cpdef long hex2int(str hexstr):
"""Convert a binary string to integer."""
# return int(hexstr, 2)
cdef bytearray binbytes = bytearray(hexstr.encode())
cdef Py_ssize_t len_ = PyByteArray_GET_SIZE(binbytes)
cdef long cumul = 0
cdef unsigned char[:] v_hexstr = binbytes
for i in range(len_):
cumul = 16*cumul + char_to_int(v_hexstr[i])
return cumul
@cython.boundscheck(False)
cpdef unsigned char df(str msg):
"""Decode Downlink Format vaule, bits 1 to 5."""
cdef str dfbin = hex2bin(msg[:2])
# return min(bin2int(dfbin[0:5]), 24)
cdef long df = bin2int(dfbin[0:5])
if df > 24:
return 24
return df
# the CRC generator
# G = [int("11111111", 2), int("11111010", 2), int("00000100", 2), int("10000000", 2)]
cdef array.array _G = array.array('l', [0b11111111, 0b11111010, 0b00000100, 0b10000000])
@cython.boundscheck(False)
@cython.wraparound(False)
cpdef long crc(str msg, bint encode=False):
"""Mode-S Cyclic Redundancy Check.
Detect if bit error occurs in the Mode-S message. When encode option is on,
the checksum is generated.
Args:
msg (string): 28 bytes hexadecimal message string
encode (bool): True to encode the date only and return the checksum
Returns:
int: message checksum, or partity bits (encoder)
"""
# the CRC generator
# G = [int("11111111", 2), int("11111010", 2), int("00000100", 2), int("10000000", 2)]
# cdef array.array _G = array.array('l', [0b11111111, 0b11111010, 0b00000100, 0b10000000])
cdef long[4] G = _G
# msgbin_split = wrap(msgbin, 8)
# mbytes = list(map(bin2int, msgbin_split))
cdef bytearray _msgbin = bytearray(hex2bin(msg).encode())
cdef unsigned char[:] msgbin = _msgbin
cdef Py_ssize_t len_msgbin = PyByteArray_GET_SIZE(_msgbin)
cdef Py_ssize_t len_mbytes = len_msgbin // 8
cdef Py_ssize_t i
if encode:
for i in range(len_msgbin - 24, len_msgbin):
msgbin[i] = 0
cdef array.array _mbytes = array.array(
'l', [bin2int(_msgbin[8*i:8*i+8].decode()) for i in range(len_mbytes)]
)
cdef long[:] mbytes = _mbytes
cdef long bits, mask
cdef Py_ssize_t ibyte, ibit
for ibyte in range(len_mbytes - 3):
for ibit in range(8):
mask = 0x80 >> ibit
bits = mbytes[ibyte] & mask
if bits > 0:
mbytes[ibyte] = mbytes[ibyte] ^ (G[0] >> ibit)
mbytes[ibyte + 1] = mbytes[ibyte + 1] ^ (
0xFF & ((G[0] << 8 - ibit) | (G[1] >> ibit))
)
mbytes[ibyte + 2] = mbytes[ibyte + 2] ^ (
0xFF & ((G[1] << 8 - ibit) | (G[2] >> ibit))
)
mbytes[ibyte + 3] = mbytes[ibyte + 3] ^ (
0xFF & ((G[2] << 8 - ibit) | (G[3] >> ibit))
)
cdef long result = (mbytes[len_mbytes-3] << 16) | (mbytes[len_mbytes-2] << 8) | mbytes[len_mbytes-1]
return result
cpdef long floor(double x):
"""Mode-S floor function.
Defined as the greatest integer value k, such that k <= x
For example: floor(3.6) = 3 and floor(-3.6) = -4
"""
return <long> c_floor(x)
cpdef str icao(str msg):
"""Calculate the ICAO address from an Mode-S message.
Applicable only with DF4, DF5, DF20, DF21 messages.
Args:
msg (String): 28 bytes hexadecimal message string
Returns:
String: ICAO address in 6 bytes hexadecimal string
"""
cdef unsigned char DF = df(msg)
cdef long c0, c1
if DF in (11, 17, 18):
addr = msg[2:8]
elif DF in (0, 4, 5, 16, 20, 21):
c0 = crc(msg, encode=True)
c1 = hex2int(msg[-6:])
addr = "%06X" % (c0 ^ c1)
else:
addr = None
return addr
cpdef bint is_icao_assigned(str icao):
"""Check whether the ICAO address is assigned (Annex 10, Vol 3)."""
if (icao is None) or (not isinstance(icao, str)) or (len(icao) != 6):
return False
cdef long icaoint = hex2int(icao)
if 0x200000 < icaoint < 0x27FFFF:
return False # AFI
if 0x280000 < icaoint < 0x28FFFF:
return False # SAM
if 0x500000 < icaoint < 0x5FFFFF:
return False # EUR, NAT
if 0x600000 < icaoint < 0x67FFFF:
return False # MID
if 0x680000 < icaoint < 0x6F0000:
return False # ASIA
if 0x900000 < icaoint < 0x9FFFFF:
return False # NAM, PAC
if 0xB00000 < icaoint < 0xBFFFFF:
return False # CAR
if 0xD00000 < icaoint < 0xDFFFFF:
return False # future
if 0xF00000 < icaoint < 0xFFFFFF:
return False # future
return True
@cython.boundscheck(False)
@cython.wraparound(False)
cpdef int typecode(str msg):
"""Type code of ADS-B message
Args:
msg (string): 28 bytes hexadecimal message string
Returns:
int: type code number
"""
if df(msg) not in (17, 18):
return -1
# return None
cdef str tcbin = hex2bin(msg[8:10])
return bin2int(tcbin[0:5])
@cython.cdivision(True)
cpdef int cprNL(double lat):
"""NL() function in CPR decoding."""
if lat == 0:
return 59
if lat == 87 or lat == -87:
return 2
if lat > 87 or lat < -87:
return 1
cdef int nz = 15
cdef double a = 1 - cos(pi / (2 * nz))
cdef double b = cos(pi / 180.0 * fabs(lat)) ** 2
cdef double nl = 2 * pi / (acos(1 - a / b))
NL = floor(nl)
return NL
@cython.boundscheck(False)
@cython.wraparound(False)
cpdef str idcode(str msg):
"""Compute identity (squawk code).
Applicable only for DF5 or DF21 messages, bit 20-32.
credit: @fbyrkjeland
Args:
msg (String): 28 bytes hexadecimal message string
Returns:
string: squawk code
"""
if df(msg) not in [5, 21]:
raise RuntimeError("Message must be Downlink Format 5 or 21.")
cdef bytearray _mbin = bytearray(hex2bin(msg).encode())
cdef unsigned char[:] mbin = _mbin
cdef bytearray _idcode = bytearray(4)
cdef unsigned char[:] idcode = _idcode
cdef unsigned char C1 = mbin[19]
cdef unsigned char A1 = mbin[20]
cdef unsigned char C2 = mbin[21]
cdef unsigned char A2 = mbin[22]
cdef unsigned char C4 = mbin[23]
cdef unsigned char A4 = mbin[24]
# _ = mbin[25]
cdef unsigned char B1 = mbin[26]
cdef unsigned char D1 = mbin[27]
cdef unsigned char B2 = mbin[28]
cdef unsigned char D2 = mbin[29]
cdef unsigned char B4 = mbin[30]
cdef unsigned char D4 = mbin[31]
# byte1 = int(A4 + A2 + A1, 2)
# byte2 = int(B4 + B2 + B1, 2)
# byte3 = int(C4 + C2 + C1, 2)
# byte4 = int(D4 + D2 + D1, 2)
idcode[0] = int_to_char((char_to_int(A4)*2 + char_to_int(A2))*2 + char_to_int(A1))
idcode[1] = int_to_char((char_to_int(B4)*2 + char_to_int(B2))*2 + char_to_int(B1))
idcode[2] = int_to_char((char_to_int(C4)*2 + char_to_int(C2))*2 + char_to_int(C1))
idcode[3] = int_to_char((char_to_int(D4)*2 + char_to_int(D2))*2 + char_to_int(D1))
return _idcode.decode()
#return str(byte1) + str(byte2) + str(byte3) + str(byte4)
@cython.boundscheck(False)
@cython.wraparound(False)
cpdef int altcode(str msg):
"""Compute the altitude.
Applicable only for DF4 or DF20 message, bit 20-32.
credit: @fbyrkjeland
Args:
msg (String): 28 bytes hexadecimal message string
Returns:
int: altitude in ft
"""
if df(msg) not in [0, 4, 16, 20]:
raise RuntimeError("Message must be Downlink Format 0, 4, 16, or 20.")
# Altitude code, bit 20-32
cdef bytearray _mbin = bytearray(hex2bin(msg).encode())
cdef unsigned char[:] mbin = _mbin
cdef char mbit = mbin[25] # M bit: 26
cdef char qbit = mbin[27] # Q bit: 28
cdef int alt = 0
cdef bytearray vbin
cdef bytearray _graybytes = bytearray(11)
cdef unsigned char[:] graybytes = _graybytes
if mbit == 48: # unit in ft, "0" -> 48
if qbit == 49: # 25ft interval, "1" -> 49
vbin = _mbin[19:25] + _mbin[26:27] + _mbin[28:32]
alt = bin2int(vbin.decode()) * 25 - 1000
if qbit == 48: # 100ft interval, above 50175ft, "0" -> 48
graybytes[8] = mbin[19]
graybytes[2] = mbin[20]
graybytes[9] = mbin[21]
graybytes[3] = mbin[22]
graybytes[10] = mbin[23]
graybytes[4] = mbin[24]
# _ = mbin[25]
graybytes[5] = mbin[26]
# cdef char D1 = mbin[27] # always zero
graybytes[6] = mbin[28]
graybytes[0] = mbin[29]
graybytes[7] = mbin[30]
graybytes[1] = mbin[31]
# graybytes = D2 + D4 + A1 + A2 + A4 + B1 + B2 + B4 + C1 + C2 + C4
alt = gray2alt(_graybytes.decode())
if mbit == 49: # unit in meter, "1" -> 49
vbin = _mbin[19:25] + _mbin[26:31]
alt = int(bin2int(vbin.decode()) * 3.28084) # convert to ft
return alt
cpdef int gray2alt(str codestr):
cdef str gc500 = codestr[:8]
cdef int n500 = gray2int(gc500)
# in 100-ft step must be converted first
cdef str gc100 = codestr[8:]
cdef int n100 = gray2int(gc100)
if n100 in [0, 5, 6]:
return -1
#return None
if n100 == 7:
n100 = 5
if n500 % 2:
n100 = 6 - n100
alt = (n500 * 500 + n100 * 100) - 1300
return alt
cdef int gray2int(str graystr):
"""Convert greycode to binary."""
cdef int num = bin2int(graystr)
num ^= num >> 8
num ^= num >> 4
num ^= num >> 2
num ^= num >> 1
return num
cdef str data(str msg):
"""Return the data frame in the message, bytes 9 to 22."""
return msg[8:-6]
cpdef bint allzeros(str msg):
"""Check if the data bits are all zeros.
Args:
msg (String): 28 bytes hexadecimal message string
Returns:
bool: True or False
"""
d = hex2bin(data(msg))
if bin2int(d) > 0:
return False
else:
return True
def wrongstatus(data, sb, msb, lsb):
"""Check if the status bit and field bits are consistency.
This Function is used for checking BDS code versions.
"""
# status bit, most significant bit, least significant bit
status = int(data[sb - 1])
value = bin2int(data[msb - 1 : lsb])
if not status:
if value != 0:
return True
return False

View File

@ -15,32 +15,11 @@ def hex2int(hexstr):
return int(hexstr, 16) return int(hexstr, 16)
def int2hex(n):
"""Convert a integer to hexadecimal string."""
# strip 'L' for python 2
return hex(n)[2:].rjust(6, "0").upper().rstrip("L")
def bin2int(binstr): def bin2int(binstr):
"""Convert a binary string to integer.""" """Convert a binary string to integer."""
return int(binstr, 2) return int(binstr, 2)
def bin2hex(hexstr):
"""Convert a hexdecimal string to integer."""
return int2hex(bin2int(hexstr))
def bin2np(binstr):
"""Convert a binary string to numpy array."""
return np.array([int(i) for i in binstr])
def np2bin(npbin):
"""Convert a binary numpy array to string."""
return np.array2string(npbin, separator="")[1:-1]
def df(msg): def df(msg):
"""Decode Downlink Format vaule, bits 1 to 5.""" """Decode Downlink Format vaule, bits 1 to 5."""
dfbin = hex2bin(msg[:2]) dfbin = hex2bin(msg[:2])
@ -100,7 +79,7 @@ def crc_legacy(msg, encode=False):
) )
ng = len(generator) ng = len(generator)
msgnpbin = bin2np(hex2bin(msg)) msgnpbin = np.array([int(i) for i in hex2bin(msg)])
if encode: if encode:
msgnpbin[-24:] = [0] * 24 msgnpbin[-24:] = [0] * 24
@ -114,7 +93,9 @@ def crc_legacy(msg, encode=False):
msgnpbin[i : i + ng] = np.bitwise_xor(msgnpbin[i : i + ng], generator) msgnpbin[i : i + ng] = np.bitwise_xor(msgnpbin[i : i + ng], generator)
# last 24 bits # last 24 bits
reminder = bin2int(np2bin(msgnpbin[-24:])) msgbin = np.array2string(msgnpbin[-24:], separator="")[1:-1]
reminder = bin2int(msgbin)
return reminder return reminder
@ -146,7 +127,7 @@ def icao(msg):
addr = msg[2:8] addr = msg[2:8]
elif DF in (0, 4, 5, 16, 20, 21): elif DF in (0, 4, 5, 16, 20, 21):
c0 = crc(msg, encode=True) c0 = crc(msg, encode=True)
c1 = hex2int(msg[-6:]) c1 = int(msg[-6:], 16)
addr = "%06X" % (c0 ^ c1) addr = "%06X" % (c0 ^ c1)
else: else:
addr = None addr = None
@ -159,7 +140,7 @@ def is_icao_assigned(icao):
if (icao is None) or (not isinstance(icao, str)) or (len(icao) != 6): if (icao is None) or (not isinstance(icao, str)) or (len(icao) != 6):
return False return False
icaoint = hex2int(icao) icaoint = int(icao, 16)
if 0x200000 < icaoint < 0x27FFFF: if 0x200000 < icaoint < 0x27FFFF:
return False # AFI return False # AFI

View File

@ -1,18 +1,3 @@
# Copyright (C) 2018 Junzi Sun (TU Delft)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
""" """
Warpper for short roll call surveillance replies DF=4/5 Warpper for short roll call surveillance replies DF=4/5

View File

@ -15,6 +15,13 @@ Steps for deploying a new verison:
# Always prefer setuptools over distutils # Always prefer setuptools over distutils
from setuptools import setup, find_packages from setuptools import setup, find_packages
# Compile some parts
from setuptools.extension import Extension
from Cython.Build import cythonize
extensions = [Extension("pyModeS.decoder.c_common", ["pyModeS/decoder/c_common.pyx"])]
# To use a consistent encoding # To use a consistent encoding
from codecs import open from codecs import open
from os import path from os import path
@ -57,6 +64,7 @@ setup(
"Programming Language :: Python :: 2", "Programming Language :: Python :: 2",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
], ],
ext_modules=cythonize(extensions),
# What does your project relate to? # What does your project relate to?
keywords="Mode-S ADS-B EHS ELS Comm-B", keywords="Mode-S ADS-B EHS ELS Comm-B",
# You can just specify the packages manually here if your project is # You can just specify the packages manually here if your project is

130
tests/benchmark.py Normal file
View File

@ -0,0 +1,130 @@
import sys
import time
import pandas as pd
from tqdm import tqdm
from pyModeS.decoder import adsb
fin = sys.argv[1]
df = pd.read_csv(fin, names=["ts", "df", "icao", "msg"])
df_adsb = df[df["df"] == 17].copy()
total = df_adsb.shape[0]
def native():
from pyModeS.decoder import common
# airborne position
m_air_0 = None
m_air_1 = None
# surface position
m_surf_0 = None
m_surf_1 = None
for i, r in tqdm(df_adsb.iterrows(), total=total):
ts = r.ts
m = r.msg
downlink_format = common.df(m)
crc = common.crc(m)
icao = adsb.icao(m)
tc = adsb.typecode(m)
if 1 <= tc <= 4:
category = adsb.category(m)
callsign = adsb.callsign(m)
if tc == 19:
velocity = adsb.velocity(m)
if 5 <= tc <= 8:
if adsb.oe_flag(m):
m_surf_1 = m
t1 = ts
else:
m_surf_0 = m
t0 = ts
if m_surf_0 and m_surf_1:
position = adsb.surface_position(
m_surf_0, m_surf_1, t0, t1, 50.01, 4.35
)
altitude = adsb.altitude(m)
if 9 <= tc <= 18:
if adsb.oe_flag(m):
m_air_1 = m
t1 = ts
else:
m_air_0 = m
t0 = ts
if m_air_0 and m_air_1:
position = adsb.position(m_air_0, m_air_1, t0, t1)
altitude = adsb.altitude(m)
def cython():
from pyModeS.decoder import c_common as common
# airborne position
m_air_0 = None
m_air_1 = None
# surface position
m_surf_0 = None
m_surf_1 = None
for i, r in tqdm(df_adsb.iterrows(), total=total):
ts = r.ts
m = r.msg
downlink_format = common.df(m)
crc = common.crc(m)
icao = adsb.icao(m)
tc = adsb.typecode(m)
if 1 <= tc <= 4:
category = adsb.category(m)
callsign = adsb.callsign(m)
if tc == 19:
velocity = adsb.velocity(m)
if 5 <= tc <= 8:
if adsb.oe_flag(m):
m_surf_1 = m
t1 = ts
else:
m_surf_0 = m
t0 = ts
if m_surf_0 and m_surf_1:
position = adsb.surface_position(
m_surf_0, m_surf_1, t0, t1, 50.01, 4.35
)
altitude = adsb.altitude(m)
if 9 <= tc <= 18:
if adsb.oe_flag(m):
m_air_1 = m
t1 = ts
else:
m_air_0 = m
t0 = ts
if m_air_0 and m_air_1:
position = adsb.position(m_air_0, m_air_1, t0, t1)
altitude = adsb.altitude(m)
if __name__ == "__main__":
t1 = time.time()
native()
dt1 = time.time() - t1
t2 = time.time()
cython()
dt2 = time.time() - t2

View File

@ -1,44 +1,46 @@
from __future__ import print_function import sys
from pyModeS import adsb, ehs import time
import csv
if len(sys.argv) > 1 and sys.argv[1] == "cython":
from pyModeS.c_decoder import adsb
else:
from pyModeS.decoder import adsb
print("===== Decode ADS-B sample data=====")
f = open("tests/data/sample_data_adsb.csv", "rt")
msg0 = None
msg1 = None
tstart = time.time()
for i, r in enumerate(csv.reader(f)):
ts = int(r[0])
m = r[1].encode()
icao = adsb.icao(m)
tc = adsb.typecode(m)
if 1 <= tc <= 4:
print(ts, m, icao, tc, adsb.category(m), adsb.callsign(m))
if tc == 19:
print(ts, m, icao, tc, adsb.velocity(m))
if 5 <= tc <= 18:
if adsb.oe_flag(m):
msg1 = m
t1 = ts
else:
msg0 = m
t0 = ts
if msg0 and msg1:
pos = adsb.position(msg0, msg1, t0, t1)
alt = adsb.altitude(m)
print(ts, m, icao, tc, pos, alt)
# === Decode sample data file === dt = time.time() - tstart
print("Execution time: {} seconds".format(dt))
def adsb_decode_all(n=None):
print("===== Decode ADS-B sample data=====")
import csv
f = open("tests/data/sample_data_adsb.csv", "rt")
msg0 = None
msg1 = None
for i, r in enumerate(csv.reader(f)):
if n and i > n:
break
ts = r[0]
m = r[1]
icao = adsb.icao(m)
tc = adsb.typecode(m)
if 1 <= tc <= 4:
print(ts, m, icao, tc, adsb.category(m), adsb.callsign(m))
if tc == 19:
print(ts, m, icao, tc, adsb.velocity(m))
if 5 <= tc <= 18:
if adsb.oe_flag(m):
msg1 = m
t1 = ts
else:
msg0 = m
t0 = ts
if msg0 and msg1:
pos = adsb.position(msg0, msg1, t0, t1)
alt = adsb.altitude(m)
print(ts, m, icao, tc, pos, alt)
if __name__ == "__main__":
adsb_decode_all(n=100)

60
tests/test_c_common.py Normal file
View File

@ -0,0 +1,60 @@
from pyModeS.decoder import c_common as common
def test_conversions():
assert common.hex2bin("6E406B") == "011011100100000001101011"
def test_crc_decode():
assert common.crc("8D406B902015A678D4D220AA4BDA") == 0
assert common.crc("8d8960ed58bf053cf11bc5932b7d") == 0
assert common.crc("8d45cab390c39509496ca9a32912") == 0
assert common.crc("8d74802958c904e6ef4ba0184d5c") == 0
assert common.crc("8d4400cd9b0000b4f87000e71a10") == 0
assert common.crc("8d4065de58a1054a7ef0218e226a") == 0
assert common.crc("c80b2dca34aa21dd821a04cb64d4") == 10719924
assert common.crc("a800089d8094e33a6004e4b8a522") == 4805588
assert common.crc("a8000614a50b6d32bed000bbe0ed") == 5659991
assert common.crc("a0000410bc900010a40000f5f477") == 11727682
assert common.crc("8d4ca251204994b1c36e60a5343d") == 16
assert common.crc("b0001718c65632b0a82040715b65") == 353333
def test_crc_encode():
parity = common.crc("8D406B902015A678D4D220AA4BDA", encode=True)
assert parity == 11160538
def test_icao():
assert common.icao("8D406B902015A678D4D220AA4BDA") == "406B90"
assert common.icao("A0001839CA3800315800007448D9") == "400940"
assert common.icao("A000139381951536E024D4CCF6B5") == "3C4DD2"
assert common.icao("A000029CFFBAA11E2004727281F1") == "4243D0"
def test_modes_altcode():
assert common.altcode("A02014B400000000000000F9D514") == 32300
def test_modes_idcode():
assert common.idcode("A800292DFFBBA9383FFCEB903D01") == "1346"
def test_graycode_to_altitude():
assert common.gray2alt("00000000010") == -1000
assert common.gray2alt("00000001010") == -500
assert common.gray2alt("00000011011") == -100
assert common.gray2alt("00000011010") == 0
assert common.gray2alt("00000011110") == 100
assert common.gray2alt("00000010011") == 600
assert common.gray2alt("00000110010") == 1000
assert common.gray2alt("00001001001") == 5800
assert common.gray2alt("00011100100") == 10300
assert common.gray2alt("01100011010") == 32000
assert common.gray2alt("01110000100") == 46300
assert common.gray2alt("01010101100") == 50200
assert common.gray2alt("11011110100") == 73200
assert common.gray2alt("10000000011") == 126600
assert common.gray2alt("10000000001") == 126700

View File

@ -1,10 +1,8 @@
from pyModeS import common from pyModeS.decoder import common
def test_conversions(): def test_conversions():
assert common.hex2bin("6E406B") == "011011100100000001101011" assert common.hex2bin("6E406B") == "011011100100000001101011"
assert common.bin2hex("011011100100000001101011") == "6E406B"
assert common.int2hex(11160538) == "AA4BDA"
def test_crc_decode(): def test_crc_decode():
@ -28,7 +26,7 @@ def test_crc_decode():
def test_crc_encode(): def test_crc_encode():
parity = common.crc("8D406B902015A678D4D220AA4BDA", encode=True) parity = common.crc("8D406B902015A678D4D220AA4BDA", encode=True)
assert common.int2hex(parity) == "AA4BDA" assert parity == 11160538
def test_icao(): def test_icao():

11
tox.ini
View File

@ -1,11 +0,0 @@
[tox]
toxworkdir = /tmp/tox
envlist = py2,py3
[testenv]
deps =
pytest
numpy
pyzmq
pyrtlsdr
commands = py.test