Merge branch 'master' into pr-71/tuftedocelot/acas
This commit is contained in:
commit
c25c9d6b96
6
Makefile
6
Makefile
@ -8,6 +8,12 @@ ext:
|
|||||||
python setup.py build_ext --inplace
|
python setup.py build_ext --inplace
|
||||||
|
|
||||||
test:
|
test:
|
||||||
|
make clean
|
||||||
|
@echo ""
|
||||||
|
@echo "[Test with py_common]"
|
||||||
|
python -m pytest tests
|
||||||
|
@echo ""
|
||||||
|
@echo "[Test with c_common]"
|
||||||
python setup.py build_ext --inplace
|
python setup.py build_ext --inplace
|
||||||
python -m pytest tests
|
python -m pytest tests
|
||||||
|
|
||||||
|
@ -17,8 +17,12 @@ cpdef bint is_icao_assigned(str icao)
|
|||||||
|
|
||||||
cpdef int typecode(str msg)
|
cpdef int typecode(str msg)
|
||||||
cpdef int cprNL(double lat)
|
cpdef int cprNL(double lat)
|
||||||
|
|
||||||
cpdef str idcode(str msg)
|
cpdef str idcode(str msg)
|
||||||
|
cpdef str squawk(str binstr)
|
||||||
|
|
||||||
cpdef int altcode(str msg)
|
cpdef int altcode(str msg)
|
||||||
|
cpdef int altitude(str binstr)
|
||||||
|
|
||||||
cpdef str data(str msg)
|
cpdef str data(str msg)
|
||||||
cpdef bint allzeros(str msg)
|
cpdef bint allzeros(str msg)
|
||||||
|
@ -160,17 +160,7 @@ cpdef long floor(double x):
|
|||||||
return <long> c_floor(x)
|
return <long> c_floor(x)
|
||||||
|
|
||||||
cpdef str icao(str msg):
|
cpdef str icao(str msg):
|
||||||
"""Calculate the ICAO address from an Mode-S message.
|
"""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 unsigned char DF = df(msg)
|
||||||
cdef long c0, c1
|
cdef long c0, c1
|
||||||
|
|
||||||
@ -217,14 +207,7 @@ cpdef bint is_icao_assigned(str icao):
|
|||||||
@cython.boundscheck(False)
|
@cython.boundscheck(False)
|
||||||
@cython.wraparound(False)
|
@cython.wraparound(False)
|
||||||
cpdef int typecode(str msg):
|
cpdef int typecode(str msg):
|
||||||
"""Type code of ADS-B message
|
"""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):
|
if df(msg) not in (17, 18):
|
||||||
return -1
|
return -1
|
||||||
# return None
|
# return None
|
||||||
@ -253,45 +236,41 @@ cpdef int cprNL(double lat):
|
|||||||
@cython.boundscheck(False)
|
@cython.boundscheck(False)
|
||||||
@cython.wraparound(False)
|
@cython.wraparound(False)
|
||||||
cpdef str idcode(str msg):
|
cpdef str idcode(str msg):
|
||||||
"""Compute identity (squawk code).
|
"""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]:
|
if df(msg) not in [5, 21]:
|
||||||
raise RuntimeError("Message must be Downlink Format 5 or 21.")
|
raise RuntimeError("Message must be Downlink Format 5 or 21.")
|
||||||
|
|
||||||
cdef bytearray _mbin = bytearray(hex2bin(msg).encode())
|
squawk_code = squawk(hex2bin(msg)[19:32])
|
||||||
|
return squawk_code
|
||||||
|
|
||||||
|
|
||||||
|
@cython.boundscheck(False)
|
||||||
|
@cython.wraparound(False)
|
||||||
|
cpdef str squawk(str binstr):
|
||||||
|
"""Compute identity (squawk code)."""
|
||||||
|
|
||||||
|
if len(binstr) != 13 or set(binstr) != set('01'):
|
||||||
|
raise RuntimeError("Input must be 13 bits binary string")
|
||||||
|
|
||||||
|
cdef bytearray _mbin = bytearray(binstr.encode())
|
||||||
cdef unsigned char[:] mbin = _mbin
|
cdef unsigned char[:] mbin = _mbin
|
||||||
|
|
||||||
cdef bytearray _idcode = bytearray(4)
|
cdef bytearray _idcode = bytearray(4)
|
||||||
cdef unsigned char[:] idcode = _idcode
|
cdef unsigned char[:] idcode = _idcode
|
||||||
|
|
||||||
cdef unsigned char C1 = mbin[19]
|
cdef unsigned char C1 = mbin[0]
|
||||||
cdef unsigned char A1 = mbin[20]
|
cdef unsigned char A1 = mbin[1]
|
||||||
cdef unsigned char C2 = mbin[21]
|
cdef unsigned char C2 = mbin[2]
|
||||||
cdef unsigned char A2 = mbin[22]
|
cdef unsigned char A2 = mbin[3]
|
||||||
cdef unsigned char C4 = mbin[23]
|
cdef unsigned char C4 = mbin[4]
|
||||||
cdef unsigned char A4 = mbin[24]
|
cdef unsigned char A4 = mbin[5]
|
||||||
# _ = mbin[25]
|
# X = mbin[6]
|
||||||
cdef unsigned char B1 = mbin[26]
|
cdef unsigned char B1 = mbin[7]
|
||||||
cdef unsigned char D1 = mbin[27]
|
cdef unsigned char D1 = mbin[8]
|
||||||
cdef unsigned char B2 = mbin[28]
|
cdef unsigned char B2 = mbin[9]
|
||||||
cdef unsigned char D2 = mbin[29]
|
cdef unsigned char D2 = mbin[10]
|
||||||
cdef unsigned char B4 = mbin[30]
|
cdef unsigned char B4 = mbin[11]
|
||||||
cdef unsigned char D4 = mbin[31]
|
cdef unsigned char D4 = mbin[12]
|
||||||
|
|
||||||
# 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[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[1] = int_to_char((char_to_int(B4)*2 + char_to_int(B2))*2 + char_to_int(B1))
|
||||||
@ -300,68 +279,68 @@ cpdef str idcode(str msg):
|
|||||||
|
|
||||||
return _idcode.decode()
|
return _idcode.decode()
|
||||||
|
|
||||||
#return str(byte1) + str(byte2) + str(byte3) + str(byte4)
|
|
||||||
|
|
||||||
|
|
||||||
@cython.boundscheck(False)
|
@cython.boundscheck(False)
|
||||||
@cython.wraparound(False)
|
@cython.wraparound(False)
|
||||||
cpdef int altcode(str msg):
|
cpdef int altcode(str msg):
|
||||||
"""Compute the altitude.
|
"""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]:
|
if df(msg) not in [0, 4, 16, 20]:
|
||||||
raise RuntimeError("Message must be Downlink Format 0, 4, 16, or 20.")
|
raise RuntimeError("Message must be Downlink Format 0, 4, 16, or 20.")
|
||||||
|
|
||||||
# Altitude code, bit 20-32
|
alt = altitude(hex2bin(msg)[19:32])
|
||||||
cdef bytearray _mbin = bytearray(hex2bin(msg).encode())
|
return alt
|
||||||
|
|
||||||
|
|
||||||
|
@cython.boundscheck(False)
|
||||||
|
@cython.wraparound(False)
|
||||||
|
cpdef int altitude(str binstr):
|
||||||
|
|
||||||
|
if len(binstr) != 13 or set(binstr) != set('01'):
|
||||||
|
raise RuntimeError("Input must be 13 bits binary string")
|
||||||
|
|
||||||
|
cdef bytearray _mbin = bytearray(binstr.encode())
|
||||||
cdef unsigned char[:] mbin = _mbin
|
cdef unsigned char[:] mbin = _mbin
|
||||||
|
|
||||||
cdef char mbit = mbin[25] # M bit: 26
|
cdef char Mbit = binstr[6]
|
||||||
cdef char qbit = mbin[27] # Q bit: 28
|
cdef char Qbit = binstr[8]
|
||||||
|
|
||||||
cdef int alt = 0
|
cdef int alt = 0
|
||||||
cdef bytearray vbin
|
cdef bytearray vbin
|
||||||
cdef bytearray _graybytes = bytearray(11)
|
cdef bytearray _graybytes = bytearray(11)
|
||||||
cdef unsigned char[:] graybytes = _graybytes
|
cdef unsigned char[:] graybytes = _graybytes
|
||||||
|
|
||||||
if mbit == 48: # unit in ft, "0" -> 48
|
if bin2int(binstr) == 0:
|
||||||
if qbit == 49: # 25ft interval, "1" -> 49
|
# altitude unknown or invalid
|
||||||
vbin = _mbin[19:25] + _mbin[26:27] + _mbin[28:32]
|
alt = -9999
|
||||||
|
|
||||||
|
elif Mbit == 48: # unit in ft, "0" -> 48
|
||||||
|
if Qbit == 49: # 25ft interval, "1" -> 49
|
||||||
|
vbin = _mbin[:6] + _mbin[7:8] + _mbin[9:]
|
||||||
alt = bin2int(vbin.decode()) * 25 - 1000
|
alt = bin2int(vbin.decode()) * 25 - 1000
|
||||||
if qbit == 48: # 100ft interval, above 50175ft, "0" -> 48
|
if Qbit == 48: # 100ft interval, above 50175ft, "0" -> 48
|
||||||
graybytes[8] = mbin[19]
|
graybytes[8] = mbin[0]
|
||||||
graybytes[2] = mbin[20]
|
graybytes[2] = mbin[1]
|
||||||
graybytes[9] = mbin[21]
|
graybytes[9] = mbin[2]
|
||||||
graybytes[3] = mbin[22]
|
graybytes[3] = mbin[3]
|
||||||
graybytes[10] = mbin[23]
|
graybytes[10] = mbin[4]
|
||||||
graybytes[4] = mbin[24]
|
graybytes[4] = mbin[5]
|
||||||
# _ = mbin[25]
|
# M = mbin[6]
|
||||||
graybytes[5] = mbin[26]
|
graybytes[5] = mbin[7]
|
||||||
# cdef char D1 = mbin[27] # always zero
|
# Q = mbin[8]
|
||||||
graybytes[6] = mbin[28]
|
graybytes[6] = mbin[9]
|
||||||
graybytes[0] = mbin[29]
|
graybytes[0] = mbin[10]
|
||||||
graybytes[7] = mbin[30]
|
graybytes[7] = mbin[11]
|
||||||
graybytes[1] = mbin[31]
|
graybytes[1] = mbin[12]
|
||||||
# graybytes = D2 + D4 + A1 + A2 + A4 + B1 + B2 + B4 + C1 + C2 + C4
|
|
||||||
|
|
||||||
alt = gray2alt(_graybytes.decode())
|
alt = gray2alt(_graybytes.decode())
|
||||||
|
|
||||||
if mbit == 49: # unit in meter, "1" -> 49
|
elif Mbit == 49: # unit in meter, "1" -> 49
|
||||||
vbin = _mbin[19:25] + _mbin[26:31]
|
vbin = _mbin[:6] + _mbin[7:]
|
||||||
alt = int(bin2int(vbin.decode()) * 3.28084) # convert to ft
|
alt = int(bin2int(vbin.decode()) * 3.28084) # convert to ft
|
||||||
|
|
||||||
return alt
|
return alt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
cpdef int gray2alt(str codestr):
|
cpdef int gray2alt(str codestr):
|
||||||
cdef str gc500 = codestr[:8]
|
cdef str gc500 = codestr[:8]
|
||||||
cdef int n500 = gray2int(gc500)
|
cdef int n500 = gray2int(gc500)
|
||||||
@ -400,15 +379,7 @@ cpdef str data(str msg):
|
|||||||
|
|
||||||
|
|
||||||
cpdef bint allzeros(str msg):
|
cpdef bint allzeros(str msg):
|
||||||
"""Check if the data bits are all zeros.
|
"""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))
|
d = hex2bin(data(msg))
|
||||||
|
|
||||||
if bin2int(d) > 0:
|
if bin2int(d) > 0:
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
def tell(msg):
|
def tell(msg: str) -> None:
|
||||||
from pyModeS import common, adsb, commb, bds
|
from pyModeS import common, adsb, commb, bds
|
||||||
|
|
||||||
def _print(label, value, unit=None):
|
def _print(label, value, unit=None):
|
||||||
|
@ -28,6 +28,7 @@ from pyModeS.decoder.bds.bds06 import (
|
|||||||
)
|
)
|
||||||
from pyModeS.decoder.bds.bds08 import category, callsign
|
from pyModeS.decoder.bds.bds08 import category, callsign
|
||||||
from pyModeS.decoder.bds.bds09 import airborne_velocity, altitude_diff
|
from pyModeS.decoder.bds.bds09 import airborne_velocity, altitude_diff
|
||||||
|
from pyModeS.decoder.bds.bds61 import is_emergency, emergency_state, emergency_squawk
|
||||||
|
|
||||||
|
|
||||||
def df(msg):
|
def df(msg):
|
||||||
|
@ -139,17 +139,13 @@ def altitude(msg):
|
|||||||
raise RuntimeError("%s: Not a airborn position message" % msg)
|
raise RuntimeError("%s: Not a airborn position message" % msg)
|
||||||
|
|
||||||
mb = common.hex2bin(msg)[32:]
|
mb = common.hex2bin(msg)[32:]
|
||||||
|
altbin = mb[8:20]
|
||||||
|
|
||||||
if tc < 19:
|
if tc < 19:
|
||||||
# barometric altitude
|
altcode = altbin[0:6] + "0" + altbin[6:]
|
||||||
q = mb[15]
|
|
||||||
if q:
|
|
||||||
n = common.bin2int(mb[8:15] + mb[16:20])
|
|
||||||
alt = n * 25 - 1000
|
|
||||||
else:
|
else:
|
||||||
alt = None
|
altcode = altbin[0:6] + "0" + altbin[6:]
|
||||||
else:
|
|
||||||
# GNSS altitude, meters -> feet
|
alt = common.altitude(altcode)
|
||||||
alt = common.bin2int(mb[8:20]) * 3.28084
|
|
||||||
|
|
||||||
return alt
|
return alt
|
||||||
|
83
pyModeS/decoder/bds/bds61.py
Normal file
83
pyModeS/decoder/bds/bds61.py
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
# ------------------------------------------
|
||||||
|
# BDS 6,1
|
||||||
|
# ADS-B TC=28
|
||||||
|
# Aircraft Airborne status
|
||||||
|
# ------------------------------------------
|
||||||
|
|
||||||
|
from pyModeS import common
|
||||||
|
|
||||||
|
|
||||||
|
def is_emergency(msg: str) -> bool:
|
||||||
|
"""Check if the aircraft is reporting an emergency.
|
||||||
|
|
||||||
|
Non-emergencies are either a subtype of zero (no information) or
|
||||||
|
subtype of one and a value of zero (no emergency).
|
||||||
|
Subtype = 2 indicates an ACAS RA broadcast, look in BDS 3,0
|
||||||
|
|
||||||
|
:param msg: 28 bytes hexadecimal message string
|
||||||
|
:return: if the aircraft has declared an emergency
|
||||||
|
"""
|
||||||
|
if common.typecode(msg) != 28:
|
||||||
|
raise RuntimeError("%s: Not an airborne status message, expecting TC=28" % msg)
|
||||||
|
|
||||||
|
mb = common.hex2bin(msg)[32:]
|
||||||
|
subtype = common.bin2int(mb[5:8])
|
||||||
|
|
||||||
|
if subtype == 2:
|
||||||
|
raise RuntimeError("%s: Emergency message is ACAS-RA, not implemented")
|
||||||
|
|
||||||
|
emergency_state = common.bin2int(mb[8:11])
|
||||||
|
|
||||||
|
if subtype == 1 and emergency_state == 1:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def emergency_state(msg: str) -> int:
|
||||||
|
"""Decode aircraft emergency state.
|
||||||
|
|
||||||
|
Value Meaning
|
||||||
|
----- -----------------------
|
||||||
|
0 No emergency
|
||||||
|
1 General emergency
|
||||||
|
2 Lifeguard/Medical
|
||||||
|
3 Minimum fuel
|
||||||
|
4 No communications
|
||||||
|
5 Unlawful communications
|
||||||
|
6-7 Reserved
|
||||||
|
|
||||||
|
:param msg: 28 bytes hexadecimal message string
|
||||||
|
:return: emergency state
|
||||||
|
"""
|
||||||
|
|
||||||
|
mb = common.hex2bin(msg)[32:]
|
||||||
|
subtype = common.bin2int(mb[5:8])
|
||||||
|
|
||||||
|
if subtype == 2:
|
||||||
|
raise RuntimeError("%s: Emergency message is ACAS-RA, not implemented")
|
||||||
|
|
||||||
|
emergency_state = common.bin2int(mb[8:11])
|
||||||
|
return emergency_state
|
||||||
|
|
||||||
|
|
||||||
|
def emergency_squawk(msg: str) -> str:
|
||||||
|
"""Decode squawk code.
|
||||||
|
|
||||||
|
Emergency value 1: squawk 7700.
|
||||||
|
Emergency value 4: squawk 7600.
|
||||||
|
Emergency value 5: squawk 7500.
|
||||||
|
|
||||||
|
:param msg: 28 bytes hexadecimal message string
|
||||||
|
:return: aircraft squawk code
|
||||||
|
"""
|
||||||
|
if common.typecode(msg) != 28:
|
||||||
|
raise RuntimeError("%s: Not an airborne status message, expecting TC=28" % msg)
|
||||||
|
|
||||||
|
msgbin = common.hex2bin(msg)
|
||||||
|
|
||||||
|
# construct the 13 bits Mode A ID code
|
||||||
|
idcode = msgbin[43:49] + "0" + msgbin[49:55]
|
||||||
|
|
||||||
|
squawk = common.squawk(idcode)
|
||||||
|
return squawk
|
@ -282,6 +282,8 @@ class TcpClient(object):
|
|||||||
|
|
||||||
# raise RuntimeError("test exception")
|
# raise RuntimeError("test exception")
|
||||||
|
|
||||||
|
except zmq.error.Again:
|
||||||
|
continue
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
tb = traceback.format_exc()
|
tb = traceback.format_exc()
|
||||||
exception_queue.put(tb)
|
exception_queue.put(tb)
|
||||||
|
@ -1,44 +1,46 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from textwrap import wrap
|
from textwrap import wrap
|
||||||
|
|
||||||
|
|
||||||
def hex2bin(hexstr):
|
def hex2bin(hexstr: str) -> str:
|
||||||
"""Convert a hexdecimal string to binary string, with zero fillings."""
|
"""Convert a hexdecimal string to binary string, with zero fillings."""
|
||||||
num_of_bits = len(hexstr) * 4
|
num_of_bits = len(hexstr) * 4
|
||||||
binstr = bin(int(hexstr, 16))[2:].zfill(int(num_of_bits))
|
binstr = bin(int(hexstr, 16))[2:].zfill(int(num_of_bits))
|
||||||
return binstr
|
return binstr
|
||||||
|
|
||||||
|
|
||||||
def hex2int(hexstr):
|
def hex2int(hexstr: str) -> int:
|
||||||
"""Convert a hexdecimal string to integer."""
|
"""Convert a hexdecimal string to integer."""
|
||||||
return int(hexstr, 16)
|
return int(hexstr, 16)
|
||||||
|
|
||||||
|
|
||||||
def bin2int(binstr):
|
def bin2int(binstr: str) -> int:
|
||||||
"""Convert a binary string to integer."""
|
"""Convert a binary string to integer."""
|
||||||
return int(binstr, 2)
|
return int(binstr, 2)
|
||||||
|
|
||||||
|
|
||||||
def bin2hex(binstr):
|
def bin2hex(binstr: str) -> str:
|
||||||
"""Convert a binary string to hexdecimal string."""
|
"""Convert a binary string to hexdecimal string."""
|
||||||
return "{0:X}".format(int(binstr, 2))
|
return "{0:X}".format(int(binstr, 2))
|
||||||
|
|
||||||
|
|
||||||
def df(msg):
|
def df(msg: str) -> int:
|
||||||
"""Decode Downlink Format value, bits 1 to 5."""
|
"""Decode Downlink Format value, bits 1 to 5."""
|
||||||
dfbin = hex2bin(msg[:2])
|
dfbin = hex2bin(msg[:2])
|
||||||
return min(bin2int(dfbin[0:5]), 24)
|
return min(bin2int(dfbin[0:5]), 24)
|
||||||
|
|
||||||
|
|
||||||
def crc(msg, encode=False):
|
def crc(msg: str, encode: bool = False) -> int:
|
||||||
"""Mode-S Cyclic Redundancy Check.
|
"""Mode-S Cyclic Redundancy Check.
|
||||||
|
|
||||||
Detect if bit error occurs in the Mode-S message. When encode option is on,
|
Detect if bit error occurs in the Mode-S message. When encode option is on,
|
||||||
the checksum is generated.
|
the checksum is generated.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (string): 28 bytes hexadecimal message string
|
msg: 28 bytes hexadecimal message string
|
||||||
encode (bool): True to encode the date only and return the checksum
|
encode: True to encode the date only and return the checksum
|
||||||
Returns:
|
Returns:
|
||||||
int: message checksum, or partity bits (encoder)
|
int: message checksum, or partity bits (encoder)
|
||||||
|
|
||||||
@ -75,7 +77,7 @@ def crc(msg, encode=False):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def crc_legacy(msg, encode=False):
|
def crc_legacy(msg: str, encode: bool = False) -> int:
|
||||||
"""Mode-S Cyclic Redundancy Check. (Legacy code, 2x slow)."""
|
"""Mode-S Cyclic Redundancy Check. (Legacy code, 2x slow)."""
|
||||||
# the polynominal generattor code for CRC [1111111111111010000001001]
|
# the polynominal generattor code for CRC [1111111111111010000001001]
|
||||||
generator = np.array(
|
generator = np.array(
|
||||||
@ -103,7 +105,7 @@ def crc_legacy(msg, encode=False):
|
|||||||
return reminder
|
return reminder
|
||||||
|
|
||||||
|
|
||||||
def floor(x):
|
def floor(x: float) -> int:
|
||||||
"""Mode-S floor function.
|
"""Mode-S floor function.
|
||||||
|
|
||||||
Defined as the greatest integer value k, such that k <= x
|
Defined as the greatest integer value k, such that k <= x
|
||||||
@ -113,7 +115,7 @@ def floor(x):
|
|||||||
return int(np.floor(x))
|
return int(np.floor(x))
|
||||||
|
|
||||||
|
|
||||||
def icao(msg):
|
def icao(msg: str) -> Optional[str]:
|
||||||
"""Calculate the ICAO address from an Mode-S message.
|
"""Calculate the ICAO address from an Mode-S message.
|
||||||
|
|
||||||
Applicable only with DF4, DF5, DF20, DF21 messages.
|
Applicable only with DF4, DF5, DF20, DF21 messages.
|
||||||
@ -125,6 +127,7 @@ def icao(msg):
|
|||||||
String: ICAO address in 6 bytes hexadecimal string
|
String: ICAO address in 6 bytes hexadecimal string
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
addr: Optional[str]
|
||||||
DF = df(msg)
|
DF = df(msg)
|
||||||
|
|
||||||
if DF in (11, 17, 18):
|
if DF in (11, 17, 18):
|
||||||
@ -139,7 +142,7 @@ def icao(msg):
|
|||||||
return addr
|
return addr
|
||||||
|
|
||||||
|
|
||||||
def is_icao_assigned(icao):
|
def is_icao_assigned(icao: str) -> bool:
|
||||||
"""Check whether the ICAO address is assigned (Annex 10, Vol 3)."""
|
"""Check whether the ICAO address is assigned (Annex 10, Vol 3)."""
|
||||||
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
|
||||||
@ -168,7 +171,7 @@ def is_icao_assigned(icao):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def typecode(msg):
|
def typecode(msg: str) -> Optional[int]:
|
||||||
"""Type code of ADS-B message
|
"""Type code of ADS-B message
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -184,7 +187,7 @@ def typecode(msg):
|
|||||||
return bin2int(tcbin[0:5])
|
return bin2int(tcbin[0:5])
|
||||||
|
|
||||||
|
|
||||||
def cprNL(lat):
|
def cprNL(lat: float) -> int:
|
||||||
"""NL() function in CPR decoding."""
|
"""NL() function in CPR decoding."""
|
||||||
|
|
||||||
if np.isclose(lat, 0):
|
if np.isclose(lat, 0):
|
||||||
@ -202,11 +205,8 @@ def cprNL(lat):
|
|||||||
return NL
|
return NL
|
||||||
|
|
||||||
|
|
||||||
def idcode(msg):
|
def idcode(msg: str) -> str:
|
||||||
"""Compute identity (squawk code).
|
"""Compute identity code (squawk) encoded in DF5 or DF21 message.
|
||||||
|
|
||||||
Applicable only for DF5 or DF21 messages, bit 20-32.
|
|
||||||
credit: @fbyrkjeland
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (String): 28 bytes hexadecimal message string
|
msg (String): 28 bytes hexadecimal message string
|
||||||
@ -219,20 +219,37 @@ def idcode(msg):
|
|||||||
raise RuntimeError("Message must be Downlink Format 5 or 21.")
|
raise RuntimeError("Message must be Downlink Format 5 or 21.")
|
||||||
|
|
||||||
mbin = hex2bin(msg)
|
mbin = hex2bin(msg)
|
||||||
|
idcodebin = mbin[19:32]
|
||||||
|
|
||||||
C1 = mbin[19]
|
return squawk(idcodebin)
|
||||||
A1 = mbin[20]
|
|
||||||
C2 = mbin[21]
|
|
||||||
A2 = mbin[22]
|
def squawk(binstr: str) -> str:
|
||||||
C4 = mbin[23]
|
"""Decode 13 bits identity (squawk) code.
|
||||||
A4 = mbin[24]
|
|
||||||
# _ = mbin[25]
|
Args:
|
||||||
B1 = mbin[26]
|
binstr (String): 13 bits binary string
|
||||||
D1 = mbin[27]
|
|
||||||
B2 = mbin[28]
|
Returns:
|
||||||
D2 = mbin[29]
|
int: altitude in ft
|
||||||
B4 = mbin[30]
|
|
||||||
D4 = mbin[31]
|
"""
|
||||||
|
if len(binstr) != 13 or set(binstr) != set("01"):
|
||||||
|
raise RuntimeError("Input must be 13 bits binary string")
|
||||||
|
|
||||||
|
C1 = binstr[0]
|
||||||
|
A1 = binstr[1]
|
||||||
|
C2 = binstr[2]
|
||||||
|
A2 = binstr[3]
|
||||||
|
C4 = binstr[4]
|
||||||
|
A4 = binstr[5]
|
||||||
|
# X = binstr[6]
|
||||||
|
B1 = binstr[7]
|
||||||
|
D1 = binstr[8]
|
||||||
|
B2 = binstr[9]
|
||||||
|
D2 = binstr[10]
|
||||||
|
B4 = binstr[11]
|
||||||
|
D4 = binstr[12]
|
||||||
|
|
||||||
byte1 = int(A4 + A2 + A1, 2)
|
byte1 = int(A4 + A2 + A1, 2)
|
||||||
byte2 = int(B4 + B2 + B1, 2)
|
byte2 = int(B4 + B2 + B1, 2)
|
||||||
@ -242,11 +259,8 @@ def idcode(msg):
|
|||||||
return str(byte1) + str(byte2) + str(byte3) + str(byte4)
|
return str(byte1) + str(byte2) + str(byte3) + str(byte4)
|
||||||
|
|
||||||
|
|
||||||
def altcode(msg):
|
def altcode(msg: str) -> Optional[int]:
|
||||||
"""Compute the altitude.
|
"""Compute altitude encoded in DF4 or DF20 message.
|
||||||
|
|
||||||
Applicable only for DF4 or DF20 message, bit 20-32.
|
|
||||||
credit: @fbyrkjeland
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (String): 28 bytes hexadecimal message string
|
msg (String): 28 bytes hexadecimal message string
|
||||||
@ -255,50 +269,78 @@ def altcode(msg):
|
|||||||
int: altitude in ft
|
int: altitude in ft
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
alt: Optional[int]
|
||||||
|
|
||||||
if df(msg) not in [0, 4, 16, 20]:
|
if df(msg) not in [0, 4, 16, 20]:
|
||||||
raise RuntimeError("Message must be Downlink Format 0, 4, 16, or 20.")
|
raise RuntimeError("Message must be Downlink Format 0, 4, 16, or 20.")
|
||||||
|
|
||||||
# Altitude code, bit 20-32
|
# Altitude code, bit 20-32
|
||||||
mbin = hex2bin(msg)
|
mbin = hex2bin(msg)
|
||||||
|
|
||||||
mbit = mbin[25] # M bit: 26
|
altitude_code = mbin[19:32]
|
||||||
qbit = mbin[27] # Q bit: 28
|
|
||||||
|
|
||||||
if mbit == "0": # unit in ft
|
alt = altitude(altitude_code)
|
||||||
if qbit == "1": # 25ft interval
|
|
||||||
vbin = mbin[19:25] + mbin[26] + mbin[28:32]
|
return alt
|
||||||
|
|
||||||
|
|
||||||
|
def altitude(binstr: str) -> Optional[int]:
|
||||||
|
"""Decode 13 bits altitude code.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
binstr (String): 13 bits binary string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: altitude in ft
|
||||||
|
|
||||||
|
"""
|
||||||
|
alt: Optional[int]
|
||||||
|
|
||||||
|
if len(binstr) != 13 or set(binstr) != set("01"):
|
||||||
|
raise RuntimeError("Input must be 13 bits binary string")
|
||||||
|
|
||||||
|
Mbit = binstr[6]
|
||||||
|
Qbit = binstr[8]
|
||||||
|
|
||||||
|
if bin2int(binstr) == 0:
|
||||||
|
# altitude unknown or invalid
|
||||||
|
alt = None
|
||||||
|
|
||||||
|
elif Mbit == "0": # unit in ft
|
||||||
|
if Qbit == "1": # 25ft interval
|
||||||
|
vbin = binstr[:6] + binstr[7] + binstr[9:]
|
||||||
alt = bin2int(vbin) * 25 - 1000
|
alt = bin2int(vbin) * 25 - 1000
|
||||||
if qbit == "0": # 100ft interval, above 50175ft
|
if Qbit == "0": # 100ft interval, above 50187.5ft
|
||||||
C1 = mbin[19]
|
C1 = binstr[0]
|
||||||
A1 = mbin[20]
|
A1 = binstr[1]
|
||||||
C2 = mbin[21]
|
C2 = binstr[2]
|
||||||
A2 = mbin[22]
|
A2 = binstr[3]
|
||||||
C4 = mbin[23]
|
C4 = binstr[4]
|
||||||
A4 = mbin[24]
|
A4 = binstr[5]
|
||||||
# _ = mbin[25]
|
# M = binstr[6]
|
||||||
B1 = mbin[26]
|
B1 = binstr[7]
|
||||||
# D1 = mbin[27] # always zero
|
# Q = binstr[8]
|
||||||
B2 = mbin[28]
|
B2 = binstr[9]
|
||||||
D2 = mbin[29]
|
D2 = binstr[10]
|
||||||
B4 = mbin[30]
|
B4 = binstr[11]
|
||||||
D4 = mbin[31]
|
D4 = binstr[12]
|
||||||
|
|
||||||
graystr = D2 + D4 + A1 + A2 + A4 + B1 + B2 + B4 + C1 + C2 + C4
|
graystr = D2 + D4 + A1 + A2 + A4 + B1 + B2 + B4 + C1 + C2 + C4
|
||||||
alt = gray2alt(graystr)
|
alt = gray2alt(graystr)
|
||||||
|
|
||||||
if mbit == "1": # unit in meter
|
if Mbit == "1": # unit in meter
|
||||||
vbin = mbin[19:25] + mbin[26:31]
|
vbin = binstr[:6] + binstr[7:]
|
||||||
alt = int(bin2int(vbin) * 3.28084) # convert to ft
|
alt = int(bin2int(vbin) * 3.28084) # convert to ft
|
||||||
|
|
||||||
return alt
|
return alt
|
||||||
|
|
||||||
|
|
||||||
def gray2alt(codestr):
|
def gray2alt(binstr: str) -> Optional[int]:
|
||||||
gc500 = codestr[:8]
|
gc500 = binstr[:8]
|
||||||
n500 = gray2int(gc500)
|
n500 = gray2int(gc500)
|
||||||
|
|
||||||
# in 100-ft step must be converted first
|
# in 100-ft step must be converted first
|
||||||
gc100 = codestr[8:]
|
gc100 = binstr[8:]
|
||||||
n100 = gray2int(gc100)
|
n100 = gray2int(gc100)
|
||||||
|
|
||||||
if n100 in [0, 5, 6]:
|
if n100 in [0, 5, 6]:
|
||||||
@ -314,9 +356,9 @@ def gray2alt(codestr):
|
|||||||
return alt
|
return alt
|
||||||
|
|
||||||
|
|
||||||
def gray2int(graystr):
|
def gray2int(binstr: str) -> int:
|
||||||
"""Convert greycode to binary."""
|
"""Convert greycode to binary."""
|
||||||
num = bin2int(graystr)
|
num = bin2int(binstr)
|
||||||
num ^= num >> 8
|
num ^= num >> 8
|
||||||
num ^= num >> 4
|
num ^= num >> 4
|
||||||
num ^= num >> 2
|
num ^= num >> 2
|
||||||
@ -324,12 +366,12 @@ def gray2int(graystr):
|
|||||||
return num
|
return num
|
||||||
|
|
||||||
|
|
||||||
def data(msg):
|
def data(msg: str) -> str:
|
||||||
"""Return the data frame in the message, bytes 9 to 22."""
|
"""Return the data frame in the message, bytes 9 to 22."""
|
||||||
return msg[8:-6]
|
return msg[8:-6]
|
||||||
|
|
||||||
|
|
||||||
def allzeros(msg):
|
def allzeros(msg: str) -> bool:
|
||||||
"""Check if the data bits are all zeros.
|
"""Check if the data bits are all zeros.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -347,7 +389,7 @@ def allzeros(msg):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def wrongstatus(data, sb, msb, lsb):
|
def wrongstatus(data: str, sb: int, msb: int, lsb: int) -> bool:
|
||||||
"""Check if the status bit and field bits are consistency.
|
"""Check if the status bit and field bits are consistency.
|
||||||
|
|
||||||
This Function is used for checking BDS code versions.
|
This Function is used for checking BDS code versions.
|
||||||
|
@ -80,6 +80,12 @@ def test_adsb_velocity():
|
|||||||
assert adsb.altitude_diff("8D485020994409940838175B284F") == 550
|
assert adsb.altitude_diff("8D485020994409940838175B284F") == 550
|
||||||
|
|
||||||
|
|
||||||
|
def test_adsb_emergency():
|
||||||
|
assert not adsb.is_emergency("8DA2C1B6E112B600000000760759")
|
||||||
|
assert adsb.emergency_state("8DA2C1B6E112B600000000760759") == 0
|
||||||
|
assert adsb.emergency_squawk("8DA2C1B6E112B600000000760759") == "6615"
|
||||||
|
|
||||||
|
|
||||||
# def test_nic():
|
# def test_nic():
|
||||||
# assert adsb.nic('8D3C70A390AB11F55B8C57F65FE6') == 0
|
# assert adsb.nic('8D3C70A390AB11F55B8C57F65FE6') == 0
|
||||||
# assert adsb.nic('8DE1C9738A4A430B427D219C8225') == 1
|
# assert adsb.nic('8DE1C9738A4A430B427D219C8225') == 1
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
from pyModeS import c_common
|
try:
|
||||||
|
from pyModeS import c_common
|
||||||
|
|
||||||
|
def test_conversions():
|
||||||
def test_conversions():
|
|
||||||
assert c_common.hex2bin("6E") == "01101110"
|
assert c_common.hex2bin("6E") == "01101110"
|
||||||
assert c_common.bin2hex("01101110") == "6E"
|
assert c_common.bin2hex("01101110") == "6E"
|
||||||
assert c_common.bin2hex("1101110") == "6E"
|
assert c_common.bin2hex("1101110") == "6E"
|
||||||
|
|
||||||
|
def test_crc_decode():
|
||||||
def test_crc_decode():
|
|
||||||
|
|
||||||
assert c_common.crc("8D406B902015A678D4D220AA4BDA") == 0
|
assert c_common.crc("8D406B902015A678D4D220AA4BDA") == 0
|
||||||
assert c_common.crc("8d8960ed58bf053cf11bc5932b7d") == 0
|
assert c_common.crc("8d8960ed58bf053cf11bc5932b7d") == 0
|
||||||
@ -23,28 +22,23 @@ def test_crc_decode():
|
|||||||
assert c_common.crc("8d4ca251204994b1c36e60a5343d") == 16
|
assert c_common.crc("8d4ca251204994b1c36e60a5343d") == 16
|
||||||
assert c_common.crc("b0001718c65632b0a82040715b65") == 353333
|
assert c_common.crc("b0001718c65632b0a82040715b65") == 353333
|
||||||
|
|
||||||
|
def test_crc_encode():
|
||||||
def test_crc_encode():
|
|
||||||
parity = c_common.crc("8D406B902015A678D4D220AA4BDA", encode=True)
|
parity = c_common.crc("8D406B902015A678D4D220AA4BDA", encode=True)
|
||||||
assert parity == 11160538
|
assert parity == 11160538
|
||||||
|
|
||||||
|
def test_icao():
|
||||||
def test_icao():
|
|
||||||
assert c_common.icao("8D406B902015A678D4D220AA4BDA") == "406B90"
|
assert c_common.icao("8D406B902015A678D4D220AA4BDA") == "406B90"
|
||||||
assert c_common.icao("A0001839CA3800315800007448D9") == "400940"
|
assert c_common.icao("A0001839CA3800315800007448D9") == "400940"
|
||||||
assert c_common.icao("A000139381951536E024D4CCF6B5") == "3C4DD2"
|
assert c_common.icao("A000139381951536E024D4CCF6B5") == "3C4DD2"
|
||||||
assert c_common.icao("A000029CFFBAA11E2004727281F1") == "4243D0"
|
assert c_common.icao("A000029CFFBAA11E2004727281F1") == "4243D0"
|
||||||
|
|
||||||
|
def test_modes_altcode():
|
||||||
def test_modes_altcode():
|
|
||||||
assert c_common.altcode("A02014B400000000000000F9D514") == 32300
|
assert c_common.altcode("A02014B400000000000000F9D514") == 32300
|
||||||
|
|
||||||
|
def test_modes_idcode():
|
||||||
def test_modes_idcode():
|
|
||||||
assert c_common.idcode("A800292DFFBBA9383FFCEB903D01") == "1346"
|
assert c_common.idcode("A800292DFFBBA9383FFCEB903D01") == "1346"
|
||||||
|
|
||||||
|
def test_graycode_to_altitude():
|
||||||
def test_graycode_to_altitude():
|
|
||||||
assert c_common.gray2alt("00000000010") == -1000
|
assert c_common.gray2alt("00000000010") == -1000
|
||||||
assert c_common.gray2alt("00000001010") == -500
|
assert c_common.gray2alt("00000001010") == -500
|
||||||
assert c_common.gray2alt("00000011011") == -100
|
assert c_common.gray2alt("00000011011") == -100
|
||||||
@ -60,3 +54,7 @@ def test_graycode_to_altitude():
|
|||||||
assert c_common.gray2alt("11011110100") == 73200
|
assert c_common.gray2alt("11011110100") == 73200
|
||||||
assert c_common.gray2alt("10000000011") == 126600
|
assert c_common.gray2alt("10000000011") == 126600
|
||||||
assert c_common.gray2alt("10000000001") == 126700
|
assert c_common.gray2alt("10000000001") == 126700
|
||||||
|
|
||||||
|
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
Loading…
Reference in New Issue
Block a user