28 Commits
v2.2 ... v2.4

Author SHA1 Message Date
Junzi Sun
26173b4038 release version 2.4 - fix import errors 2019-09-24 21:26:35 +02:00
Junzi Sun
3619d52760 release version 2.3 2019-09-24 16:34:37 +02:00
Junzi Sun
e2ece806c2 update tell() 2019-09-24 16:30:38 +02:00
Junzi Sun
a683e40c41 deprecation warning only one time 2019-09-11 16:01:55 +02:00
Junzi Sun
0c1a3b06e1 Merge branch 'master' of github.com:junzis/pyModeS 2019-09-11 15:39:00 +02:00
Junzi Sun
f960cd71bc move dependencies to extras 2019-09-11 15:36:03 +02:00
Junzi Sun
bd54ac1d10 add tell() function 2019-09-11 14:58:31 +02:00
Junzi Sun
2c1db13122 reformat code with Black 2019-09-10 23:25:21 +02:00
Junzi Sun
695fc34988 update airborne_position(), allow swapping the odd and even messages 2019-09-10 14:47:15 +02:00
Junzi Sun
0eb333ba8c deal with Python 2 long int 2019-09-10 13:31:27 +02:00
Junzi Sun
d058e9f8b3 Merge pull request #49 from espinielli/patch-1
moved pms.adsb.surface_velocity under typecode 5-8
2019-09-06 23:01:47 +02:00
Enrico Spinielli
3d99deb049 moved pms.adsb.surface_velocity under typecode 5-8
I tried to `pms.adsb.surface_velocity()` as per README with a typecode 19 msg and got an error:

```python
>>> import pyModeS as pms
>>> msg = '8d484966990076b9e0762d5ff92c'
>>> pms.adsb.typecode(msg)
19
>>> pms.adsb.surface_velocity(msg)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Users\spi\AppData\Roaming\Python\Python27\site-packages\pyModeS\decoder\bds\bds06.py", line 162, in surface_velocity
    raise RuntimeError("%s: Not a surface message, expecting 5<TC<8" % msg)
RuntimeError: 8d484966990076b9e0762d5ff92c: Not a surface message, expecting 5<TC<8
```

so I propose this fix.
I used pyModeS      2.2
2019-09-05 16:56:42 +02:00
Junzi Sun
03f81d120b Merge pull request #48 from richardjanssen/master
Fix surface positions with negative longitude
2019-09-03 22:14:41 +02:00
Junzi Sun
b9b95320d8 keep only common virtualenv folder names 2019-09-03 22:12:30 +02:00
Richard Janssen
0ea2cf7ade Fix surface positions for negative longitude 2019-09-03 12:15:34 +02:00
Junzi Sun
458b02028d update readme 2019-08-26 16:50:05 +02:00
Junzi Sun
b062bdf998 fix local buffer bug 2019-08-26 16:38:36 +02:00
Junzi Sun
cfcd21b692 improve multiprocessing efficiency 2019-08-26 12:00:14 +02:00
Junzi Sun
86f302f05e change modeslive to multiprocessing 2019-08-26 00:21:29 +02:00
Junzi Sun
28a6e53d49 update dependencies 2019-08-23 14:59:50 +02:00
Junzi Sun
81d7cef6e8 Merge pull request #47 from junzis/rtlsdr
Add RTL-SDR support
2019-08-23 14:47:50 +02:00
Junzi Sun
4906a49e9c update modeslive command 2019-08-23 14:46:13 +02:00
Junzi Sun
7cb75ea8ca Update README.rst 2019-08-22 19:47:20 +02:00
Junzi Sun
785584aff5 improve singal process efficiency 2019-08-22 16:59:12 +02:00
Junzi Sun
fdc34497c0 update instruction for rtl-sdr 2019-08-22 10:51:11 +02:00
Junzi Sun
4a3c9438f7 adding support for rtl-sdr tunner 2019-08-22 10:45:12 +02:00
Junzi Sun
b723374337 Merge pull request #45 from hegrin-cz/master
Corrected ADS-B emitter category parsing.
2019-08-22 10:37:58 +02:00
Hegr,Jiri
8f4dff5b30 Corrected category parsing. 2019-08-19 14:50:09 +02:00
41 changed files with 1721 additions and 985 deletions

6
.gitignore vendored
View File

@@ -58,3 +58,9 @@ target/
# PyCharm
.idea/
# Environments
.env
.venv
env/
venv/

View File

@@ -1,9 +1,7 @@
The Python ADS-B/Mode-S Decoder
===============================
If you find this project useful for your research, please cite our work (bibtex format):
::
If you find this project useful for your research, please considering cite this tool as::
@article{sun2019pymodes,
author={J. {Sun} and H. {V\^u} and J. {Ellerbroek} and J. M. {Hoekstra}},
@@ -18,11 +16,11 @@ If you find this project useful for your research, please cite our work (bibtex
Introduction
---------------------
PyModeS is a Python library designed to decode Mode-S (including ADS-B) message.
Message with following Downlink Formats (DF) are supported:
PyModeS is a Python library designed to decode Mode-S (including ADS-B) message. It can be imported to your python project or be used as a standalone tool to view and save live traffic data.
Messages with following Downlink Formats (DF) are supported:
**DF17 / DF18: Automatic Dependent Surveillance - Broadcast (ADS-B)**
**DF17 / DF18: Automatic Dependent Surveillance-Broadcast (ADS-B)**
- TC=1-4 / BDS 0,8: Aircraft identification and category
- TC=5-8 / BDS 0,6: Surface position
@@ -53,13 +51,13 @@ Message with following Downlink Formats (DF) are supported:
Resources
-----------
Checkout and contribute to this open-source project at:
Check out and contribute to this open-source project at:
https://github.com/junzis/pyModeS
Detailed manual on Mode-S decoding is published at:
https://mode-s.org/decode.
API documentation of pyModeS is at:
The API documentation of pyModeS is at:
http://pymodes.readthedocs.io
@@ -67,44 +65,61 @@ http://pymodes.readthedocs.io
Install
-------
To install latest version from the GitHub:
The pyModeS can be installed with extra option ``[all]`` in order to install dependencies ``pyzmq`` and ``pyrtlsdr`` automatically.
::
pip install git+https://github.com/junzis/pyModeS
To install the stable version (2.0) from pip:
::
Installation examples::
# stable version, basic
pip install pyModeS
# stable version, including dependencies for streamer and rtlsdr
pip install pyModeS[all]
# development version, basic
pip install git+https://github.com/junzis/pyModeS
# development version, including dependencies for streamer and rtlsdr
pip install git+https://github.com/junzis/pyModeS#egg=pyModeS[all]
Live view traffic (modeslive)
View live traffic (modeslive)
----------------------------------------------------
Supports **Mode-S Beast** and **AVR** raw stream
::
General usage::
modeslive --server [server_address] --port [tcp_port] --rawtype [beast,avr,skysense] --latlon [lat] [lon] --dumpto [folder]
$ modeslive [-h] --source SOURCE [--connect SERVER PORT DATAYPE]
[--latlon LAT LON] [--show-uncertainty] [--dumpto DUMPTO]
Arguments:
-h, --help show this help message and exit
--server SERVER server address or IP
--port PORT raw data port
--rawtype RAWTYPE beast, avr or skysense
--latlon LAT LON receiver position
--show-uncertainty display uncertaint values, default off
--dumpto folder to dump decoded output
arguments:
-h, --help show this help message and exit
--source SOURCE Choose data source, "rtlsdr" or "net"
--connect SERVER PORT DATATYPE
Define server, port and data type. Supported data
types are: ['raw', 'beast', 'skysense']
--latlon LAT LON Receiver latitude and longitude, needed for the surface
position, default none
--show-uncertainty Display uncertainty values, default off
--dumpto DUMPTO Folder to dump decoded output, default none
If you have a RTL-SDR receiver or Mode-S Beast, use modesmixer2 (http://xdeco.org/?page_id=48) to create raw beast TCP stream:
Live with RTL-SDR
*******************
If you have an RTL-SDR receiver plugged to the computer, you can connect it with ``rtlsdr`` source switch, shown as follows::
$ modeslive --source rtlsdr
Live with network data
***************************
If you want to connect to a TCP server that broadcast raw data. use can use ``net`` source switch, for example::
$ modeslive --source net --connect localhost 30002 raw
$ modeslive --source net --connect 127.0.0.1 30005 beast
::
$ modesmixer2 --inSeriel port[:speed[:flow_control]] --outServer beast:[tcp_port]
Example screenshot:
@@ -150,6 +165,7 @@ Core functions for ADS-B decoding
pms.adsb.position(msg_even, msg_odd, t_even, t_odd, lat_ref=None, lon_ref=None)
pms.adsb.airborne_position(msg_even, msg_odd, t_even, t_odd)
pms.adsb.surface_position(msg_even, msg_odd, t_even, t_odd, lat_ref, lon_ref)
pms.adsb.surface_velocity(msg)
pms.adsb.position_with_ref(msg, lat_ref, lon_ref)
pms.adsb.airborne_position_with_ref(msg, lat_ref, lon_ref)
@@ -160,15 +176,10 @@ Core functions for ADS-B decoding
# Typecode: 19
pms.adsb.velocity(msg) # Handles both surface & airborne messages
pms.adsb.speed_heading(msg) # Handles both surface & airborne messages
pms.adsb.surface_velocity(msg)
pms.adsb.airborne_velocity(msg)
Note: When you have a fix position of the aircraft, it is convenient to
use `position_with_ref()` method to decode with only one position message
(either odd or even). This works with both airborne and surface position
messages. But the reference position shall be with in 180NM (airborne)
or 45NM (surface) of the true position.
Note: When you have a fix position of the aircraft, it is convenient to use `position_with_ref()` method to decode with only one position message (either odd or even). This works with both airborne and surface position messages. But the reference position shall be within 180NM (airborne) or 45NM (surface) of the true position.
Decode altitude replies in DF4 / DF20
@@ -275,49 +286,48 @@ Meteorological hazard air report (MHR) [Experimental]
Customize the streaming module
******************************
The TCP client module from pyModeS can be re-used to stream and process Mode-S
data as your like. You need to re-implement the ``handle_messages()`` function from
the ``BaseClient`` class to write your own logic to handle the messages.
The TCP client module from pyModeS can be re-used to stream and process Mode-S data as you like. You need to re-implement the ``handle_messages()`` function from the ``TcpClient`` class to write your own logic to handle the messages.
Here is an example:
.. code:: python
from pyModeS.extra.tcpclient import BaseClient
import pyModeS as pms
from pyModeS.extra.tcpclient import TcpClient
# define your custom class by extending the BaseClient
# define your custom class by extending the TcpClient
# - implement your handle_messages() methods
class ADSBClient(BaseClient):
class ADSBClient(TcpClient):
def __init__(self, host, port, rawtype):
super(ModesClient, self).__init__(host, port, rawtype)
super(ADSBClient, self).__init__(host, port, rawtype)
def handle_messages(self, messages):
for msg, ts in messages:
if len(msg) < 28: # wrong data length
if len(msg) != 28: # wrong data length
continue
df = pms.df(msg)
if df != 17: # not ADSB
if df != 17: # not ADSB
continue
if '1' in pms.crc(msg): # CRC fail
if pms.crc(msg) !=0: # CRC fail
continue
icao = pms.adsb.icao(msg)
tc = pms.adsb.typecode(msg)
# TODO: write you magic code here
print ts, icao, tc, msg
print(ts, icao, tc, msg)
# run new client, change the host, port, and rawtype if needed
client = ADSBClient(host='127.0.0.1', port=30334, rawtype='beast')
client = ADSBClient(host='127.0.0.1', port=30005, rawtype='beast')
client.run()
Unit test
---------
To perform unit tests. First install ``tox`` through pip, Then, run the following commands:
To perform unit tests. First, install ``tox`` through pip. Then, run the following commands:
.. code:: bash

View File

@@ -1,6 +1,10 @@
from __future__ import absolute_import, print_function, division
import os
import warnings
from .decoder.common import *
from .decoder import tell
from .decoder import adsb
from .decoder import commb
from .decoder import common
@@ -8,8 +12,6 @@ from .decoder import bds
from .extra import aero
from .extra import tcpclient
# from .decoder import els # depricated
# from .decoder import ehs # depricated
warnings.simplefilter("once", DeprecationWarning)
import os
dirpath = os.path.dirname(os.path.realpath(__file__))

View File

@@ -1,3 +1,145 @@
from __future__ import absolute_import, print_function, division
from pyModeS.decoder import *
from pyModeS.decoder import adsb, commb, common, bds
def tell(msg):
def _print(label, value, unit=None):
print("%20s: " % label, end="")
print("%s " % value, end="")
if unit:
print(unit)
else:
print()
df = common.df(msg)
icao = common.icao(msg)
_print("Message", msg)
_print("ICAO address", icao)
_print("Downlink Format", df)
if df == 17:
_print("Protocal", "Mode-S Extended Squitter (ADS-B)")
tc = common.typecode(msg)
if 1 <= tc <= 4: # callsign
callsign = adsb.callsign(msg)
_print("Type", "Identitification and category")
_print("Callsign:", callsign)
if 5 <= tc <= 8: # surface position
_print("Type", "Surface postition")
oe = adsb.oe_flag(msg)
msgbin = common.hex2bin(msg)
cprlat = common.bin2int(msgbin[54:71]) / 131072.0
cprlon = common.bin2int(msgbin[71:88]) / 131072.0
v = adsb.surface_velocity(msg)
_print("CPR format", "Odd" if oe else "Even")
_print("CPR Latitude", cprlat)
_print("CPR Longitude", cprlon)
_print("Speed", v[0], "knots")
_print("Track", v[1], "degrees")
if 9 <= tc <= 18: # airborne position
_print("Type", "Airborne position (with barometric altitude)")
alt = adsb.altitude(msg)
oe = adsb.oe_flag(msg)
msgbin = common.hex2bin(msg)
cprlat = common.bin2int(msgbin[54:71]) / 131072.0
cprlon = common.bin2int(msgbin[71:88]) / 131072.0
_print("CPR format", "Odd" if oe else "Even")
_print("CPR Latitude", cprlat)
_print("CPR Longitude", cprlon)
_print("Altitude", alt, "feet")
if tc == 19:
_print("Type", "Airborne velocity")
spd, trk, vr, t = adsb.velocity(msg)
types = {"GS": "Ground speed", "TAS": "True airspeed"}
_print("Speed", spd, "knots")
_print("Track", trk, "degrees")
_print("Vertical rate", vr, "feet/minute")
_print("Type", types[t])
if 20 <= tc <= 22: # airborne position
_print("Type", "Airborne position (with GNSS altitude)")
alt = adsb.altitude(msg)
oe = adsb.oe_flag(msg)
msgbin = common.hex2bin(msg)
cprlat = common.bin2int(msgbin[54:71]) / 131072.0
cprlon = common.bin2int(msgbin[71:88]) / 131072.0
_print("CPR format", "Odd" if oe else "Even")
_print("CPR Latitude", cprlat)
_print("CPR Longitude", cprlon)
_print("Altitude", alt, "feet")
if df == 20:
_print("Protocal", "Mode-S Comm-B altitude reply")
_print("Altitude", common.altcode(msg), "feet")
if df == 21:
_print("Protocal", "Mode-S Comm-B identity reply")
_print("Squawk code", common.idcode(msg))
if df == 20 or df == 21:
labels = {
"BDS10": "Data link capability",
"BDS17": "GICB capability",
"BDS20": "Aircraft identification",
"BDS30": "ACAS resolution",
"BDS40": "Vertical intention report",
"BDS50": "Track and turn report",
"BDS60": "Heading and speed report",
"BDS44": "Meteorological routine air report",
"BDS45": "Meteorological hazard report",
"EMPTY": "[No information available]",
}
BDS = bds.infer(msg, mrar=True)
if BDS in labels.keys():
_print("BDS", "%s (%s)" % (BDS, labels[BDS]))
else:
_print("BDS", BDS)
if BDS == "BDS20":
callsign = commb.cs20(msg)
_print("Callsign", callsign)
if BDS == "BDS40":
_print("MCP target alt", commb.selalt40mcp(msg), "feet")
_print("FMS Target alt", commb.selalt40fms(msg), "feet")
_print("Pressure", commb.p40baro(msg), "millibar")
if BDS == "BDS50":
_print("Roll angle", commb.roll50(msg), "degrees")
_print("Track angle", commb.trk50(msg), "degrees")
_print("Track rate", commb.rtrk50(msg), "degree/second")
_print("Ground speed", commb.gs50(msg), "knots")
_print("True airspeed", commb.tas50(msg), "knots")
if BDS == "BDS60":
_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")
_print("Vertical rate (INS)", commb.vr60ins(msg), "feet/minute")
if BDS == "BDS44":
_print("Wind speed", commb.wind44(msg)[0], "knots")
_print("Wind direction", commb.wind44(msg)[1], "degrees")
_print("Temperature 1", commb.temp44(msg)[0], "Celsius")
_print("Temperature 2", commb.temp44(msg)[1], "Celsius")
_print("Pressure", commb.p44(msg), "hPa")
_print("Humidity", commb.hum44(msg), "%")
_print("Turbulence", commb.turb44(msg))
if BDS == "BDS45":
_print("Turbulence", commb.turb45(msg))
_print("Wind shear", commb.ws45(msg))
_print("Microbust", commb.mb45(msg))
_print("Icing", commb.ic45(msg))
_print("Wake vortex", commb.wv45(msg))
_print("Temperature", commb.temp45(msg), "Celsius")
_print("Pressure", commb.p45(msg), "hPa")
_print("Radio height", commb.rh45(msg), "feet")

View File

@@ -35,20 +35,32 @@ from pyModeS.decoder import common
from pyModeS.decoder import uncertainty
# from pyModeS.decoder.bds import bds05, bds06, bds09
from pyModeS.decoder.bds.bds05 import airborne_position, airborne_position_with_ref, altitude
from pyModeS.decoder.bds.bds06 import surface_position, surface_position_with_ref, surface_velocity
from pyModeS.decoder.bds.bds05 import (
airborne_position,
airborne_position_with_ref,
altitude,
)
from pyModeS.decoder.bds.bds06 import (
surface_position,
surface_position_with_ref,
surface_velocity,
)
from pyModeS.decoder.bds.bds08 import category, callsign
from pyModeS.decoder.bds.bds09 import airborne_velocity, altitude_diff
def df(msg):
return common.df(msg)
def icao(msg):
return common.icao(msg)
def typecode(msg):
return common.typecode(msg)
def position(msg0, msg1, t0, t1, lat_ref=None, lon_ref=None):
"""Decode position from a pair of even and odd position message
(works with both airborne and surface position messages)
@@ -65,19 +77,21 @@ def position(msg0, msg1, t0, t1, lat_ref=None, lon_ref=None):
tc0 = typecode(msg0)
tc1 = typecode(msg1)
if (5<=tc0<=8 and 5<=tc1<=8):
if 5 <= tc0 <= 8 and 5 <= tc1 <= 8:
if (not lat_ref) or (not lon_ref):
raise RuntimeError("Surface position encountered, a reference \
raise RuntimeError(
"Surface position encountered, a reference \
position lat/lon required. Location of \
receiver can be used.")
receiver can be used."
)
else:
return surface_position(msg0, msg1, t0, t1, lat_ref, lon_ref)
elif (9<=tc0<=18 and 9<=tc1<=18):
elif 9 <= tc0 <= 18 and 9 <= tc1 <= 18:
# Airborne position with barometric height
return airborne_position(msg0, msg1, t0, t1)
elif (20<=tc0<=22 and 20<=tc1<=22):
elif 20 <= tc0 <= 22 and 20 <= tc1 <= 22:
# Airborne position with GNSS height
return airborne_position(msg0, msg1, t0, t1)
@@ -104,10 +118,10 @@ def position_with_ref(msg, lat_ref, lon_ref):
tc = typecode(msg)
if 5<=tc<=8:
if 5 <= tc <= 8:
return surface_position_with_ref(msg, lat_ref, lon_ref)
elif 9<=tc<=18 or 20<=tc<=22:
elif 9 <= tc <= 18 or 20 <= tc <= 22:
return airborne_position_with_ref(msg, lat_ref, lon_ref)
else:
@@ -126,17 +140,17 @@ def altitude(msg):
tc = typecode(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)
if tc>=5 and tc<=8:
if tc >= 5 and tc <= 8:
# surface position, altitude 0
return 0
msgbin = common.hex2bin(msg)
q = msgbin[47]
if q:
n = common.bin2int(msgbin[40:47]+msgbin[48:52])
n = common.bin2int(msgbin[40:47] + msgbin[48:52])
alt = n * 25 - 1000
return alt
else:
@@ -175,7 +189,9 @@ def velocity(msg, rtn_sources=False):
return airborne_velocity(msg, rtn_sources)
else:
raise RuntimeError("incorrect or inconsistant message types, expecting 4<TC<9 or TC=19")
raise RuntimeError(
"incorrect or inconsistant message types, expecting 4<TC<9 or TC=19"
)
def speed_heading(msg):
@@ -215,7 +231,9 @@ def version(msg):
tc = typecode(msg)
if tc != 31:
raise RuntimeError("%s: Not a status operation message, expecting TC = 31" % msg)
raise RuntimeError(
"%s: Not a status operation message, expecting TC = 31" % msg
)
msgbin = common.hex2bin(msg)
version = common.bin2int(msgbin[72:75])
@@ -241,18 +259,18 @@ def nuc_p(msg):
raise RuntimeError(
"%s: Not a surface position message (5<TC<8), \
airborne position message (8<TC<19), \
or airborne position with GNSS height (20<TC<22)" % msg
or airborne position with GNSS height (20<TC<22)"
% msg
)
try:
NUCp = uncertainty.TC_NUCp_lookup[tc]
HPL = uncertainty.NUCp[NUCp]['HPL']
RCu = uncertainty.NUCp[NUCp]['RCu']
RCv = uncertainty.NUCp[NUCp]['RCv']
HPL = uncertainty.NUCp[NUCp]["HPL"]
RCu = uncertainty.NUCp[NUCp]["RCu"]
RCv = uncertainty.NUCp[NUCp]["RCv"]
except KeyError:
HPL, RCu, RCv = uncertainty.NA, uncertainty.NA, uncertainty.NA
if tc in [20, 21]:
RCv = uncertainty.NA
@@ -272,15 +290,16 @@ def nuc_v(msg):
tc = typecode(msg)
if tc != 19:
raise RuntimeError("%s: Not an airborne velocity message, expecting TC = 19" % msg)
raise RuntimeError(
"%s: Not an airborne velocity message, expecting TC = 19" % msg
)
msgbin = common.hex2bin(msg)
NUCv = common.bin2int(msgbin[42:45])
try:
HVE = uncertainty.NUCv[NUCv]['HVE']
VVE = uncertainty.NUCv[NUCv]['VVE']
HVE = uncertainty.NUCv[NUCv]["HVE"]
VVE = uncertainty.NUCv[NUCv]["VVE"]
except KeyError:
HVE, VVE = uncertainty.NA, uncertainty.NA
@@ -302,7 +321,8 @@ def nic_v1(msg, NICs):
raise RuntimeError(
"%s: Not a surface position message (5<TC<8), \
airborne position message (8<TC<19), \
or airborne position with GNSS height (20<TC<22)" % msg
or airborne position with GNSS height (20<TC<22)"
% msg
)
tc = typecode(msg)
@@ -312,8 +332,8 @@ def nic_v1(msg, NICs):
NIC = NIC[NICs]
try:
Rc = uncertainty.NICv1[NIC][NICs]['Rc']
VPL = uncertainty.NICv1[NIC][NICs]['VPL']
Rc = uncertainty.NICv1[NIC][NICs]["Rc"]
VPL = uncertainty.NICv1[NIC][NICs]["VPL"]
except KeyError:
Rc, VPL = uncertainty.NA, uncertainty.NA
@@ -335,22 +355,23 @@ def nic_v2(msg, NICa, NICbc):
raise RuntimeError(
"%s: Not a surface position message (5<TC<8), \
airborne position message (8<TC<19), \
or airborne position with GNSS height (20<TC<22)" % msg
or airborne position with GNSS height (20<TC<22)"
% msg
)
tc = typecode(msg)
NIC = uncertainty.TC_NICv2_lookup[tc]
if 20<=tc<=22:
if 20 <= tc <= 22:
NICs = 0
else:
NICs = NICa*2 + NICbc
NICs = NICa * 2 + NICbc
try:
if isinstance(NIC, dict):
NIC = NIC[NICs]
Rc = uncertainty.NICv2[NIC][NICs]['Rc']
Rc = uncertainty.NICv2[NIC][NICs]["Rc"]
except KeyError:
Rc = uncertainty.NA
@@ -369,7 +390,9 @@ def nic_s(msg):
tc = typecode(msg)
if tc != 31:
raise RuntimeError("%s: Not a status operation message, expecting TC = 31" % msg)
raise RuntimeError(
"%s: Not a status operation message, expecting TC = 31" % msg
)
msgbin = common.hex2bin(msg)
nic_s = int(msgbin[75])
@@ -389,7 +412,9 @@ def nic_a_c(msg):
tc = typecode(msg)
if tc != 31:
raise RuntimeError("%s: Not a status operation message, expecting TC = 31" % msg)
raise RuntimeError(
"%s: Not a status operation message, expecting TC = 31" % msg
)
msgbin = common.hex2bin(msg)
nic_a = int(msgbin[75])
@@ -410,7 +435,9 @@ def nic_b(msg):
tc = typecode(msg)
if tc < 9 or tc > 18:
raise RuntimeError("%s: Not a airborne position message, expecting 8<TC<19" % msg)
raise RuntimeError(
"%s: Not a airborne position message, expecting 8<TC<19" % msg
)
msgbin = common.hex2bin(msg)
nic_b = int(msgbin[39])
@@ -431,8 +458,11 @@ def nac_p(msg):
tc = typecode(msg)
if tc not in [29, 31]:
raise RuntimeError("%s: Not a target state and status message, \
or operation status message, expecting TC = 29 or 31" % msg)
raise RuntimeError(
"%s: Not a target state and status message, \
or operation status message, expecting TC = 29 or 31"
% msg
)
msgbin = common.hex2bin(msg)
@@ -442,8 +472,8 @@ def nac_p(msg):
NACp = common.bin2int(msgbin[76:80])
try:
EPU = uncertainty.NACp[NACp]['EPU']
VEPU = uncertainty.NACp[NACp]['VEPU']
EPU = uncertainty.NACp[NACp]["EPU"]
VEPU = uncertainty.NACp[NACp]["VEPU"]
except KeyError:
EPU, VEPU = uncertainty.NA, uncertainty.NA
@@ -463,14 +493,16 @@ def nac_v(msg):
tc = typecode(msg)
if tc != 19:
raise RuntimeError("%s: Not an airborne velocity message, expecting TC = 19" % msg)
raise RuntimeError(
"%s: Not an airborne velocity message, expecting TC = 19" % msg
)
msgbin = common.hex2bin(msg)
NACv = common.bin2int(msgbin[42:45])
try:
HFOMr = uncertainty.NACv[NACv]['HFOMr']
VFOMr = uncertainty.NACv[NACv]['VFOMr']
HFOMr = uncertainty.NACv[NACv]["HFOMr"]
VFOMr = uncertainty.NACv[NACv]["VFOMr"]
except KeyError:
HFOMr, VFOMr = uncertainty.NA, uncertainty.NA
@@ -491,8 +523,11 @@ def sil(msg, version):
tc = typecode(msg)
if tc not in [29, 31]:
raise RuntimeError("%s: Not a target state and status messag, \
or operation status message, expecting TC = 29 or 31" % msg)
raise RuntimeError(
"%s: Not a target state and status messag, \
or operation status message, expecting TC = 29 or 31"
% msg
)
msgbin = common.hex2bin(msg)
@@ -502,12 +537,12 @@ def sil(msg, version):
SIL = common.bin2int(msgbin[82:84])
try:
PE_RCu = uncertainty.SIL[SIL]['PE_RCu']
PE_VPL = uncertainty.SIL[SIL]['PE_VPL']
PE_RCu = uncertainty.SIL[SIL]["PE_RCu"]
PE_VPL = uncertainty.SIL[SIL]["PE_VPL"]
except KeyError:
PE_RCu, PE_VPL = uncertainty.NA, uncertainty.NA
base = 'unknown'
base = "unknown"
if version == 2:
if tc == 29:

View File

@@ -23,8 +23,22 @@ import numpy as np
from pyModeS.extra import aero
from pyModeS.decoder import common
from pyModeS.decoder.bds import bds05, bds06, bds08, bds09, \
bds10, bds17, bds20, bds30, bds40, bds44, bds45, bds50, bds53, bds60
from pyModeS.decoder.bds import (
bds05,
bds06,
bds08,
bds09,
bds10,
bds17,
bds20,
bds30,
bds40,
bds44,
bds45,
bds50,
bds53,
bds60,
)
def is50or60(msg, spd_ref, trk_ref, alt_ref):
@@ -40,6 +54,7 @@ def is50or60(msg, spd_ref, trk_ref, alt_ref):
String or None: BDS version, or possible versions, or None if nothing matches.
"""
def vxy(v, angle):
vx = v * np.sin(np.radians(angle))
vy = v * np.cos(np.radians(angle))
@@ -52,26 +67,26 @@ def is50or60(msg, spd_ref, trk_ref, alt_ref):
v50 = bds50.gs50(msg)
if h50 is None or v50 is None:
return 'BDS50,BDS60'
return "BDS50,BDS60"
h60 = bds60.hdg60(msg)
m60 = bds60.mach60(msg)
i60 = bds60.ias60(msg)
if h60 is None or (m60 is None and i60 is None):
return 'BDS50,BDS60'
return "BDS50,BDS60"
m60 = np.nan if m60 is None else m60
i60 = np.nan if i60 is None else i60
XY5 = vxy(v50*aero.kts, h50)
XY6m = vxy(aero.mach2tas(m60, alt_ref*aero.ft), h60)
XY6i = vxy(aero.cas2tas(i60*aero.kts, alt_ref*aero.ft), h60)
XY5 = vxy(v50 * aero.kts, h50)
XY6m = vxy(aero.mach2tas(m60, alt_ref * aero.ft), h60)
XY6i = vxy(aero.cas2tas(i60 * aero.kts, alt_ref * aero.ft), h60)
allbds = ['BDS50', 'BDS60', 'BDS60']
allbds = ["BDS50", "BDS60", "BDS60"]
X = np.array([XY5, XY6m, XY6i])
Mu = np.array(vxy(spd_ref*aero.kts, trk_ref))
Mu = np.array(vxy(spd_ref * aero.kts, trk_ref))
# compute Mahalanobis distance matrix
# Cov = [[20**2, 0], [0, 20**2]]
@@ -81,10 +96,10 @@ def is50or60(msg, spd_ref, trk_ref, alt_ref):
# since the covariance matrix is identity matrix,
# M-dist is same as eculidian distance
try:
dist = np.linalg.norm(X-Mu, axis=1)
dist = np.linalg.norm(X - Mu, axis=1)
BDS = allbds[np.nanargmin(dist)]
except ValueError:
return 'BDS50,BDS60'
return "BDS50,BDS60"
return BDS
@@ -103,28 +118,28 @@ def infer(msg, mrar=False):
df = common.df(msg)
if common.allzeros(msg):
return 'EMPTY'
return "EMPTY"
# For ADS-B / Mode-S extended squitter
if df == 17:
tc = common.typecode(msg)
if 1 <= tc <= 4:
return 'BDS08' # indentification and category
return "BDS08" # indentification and category
if 5 <= tc <= 8:
return 'BDS06' # surface movement
return "BDS06" # surface movement
if 9 <= tc <= 18:
return 'BDS05' # airborne position, baro-alt
return "BDS05" # airborne position, baro-alt
if tc == 19:
return 'BDS09' # airborne velocity
return "BDS09" # airborne velocity
if 20 <= tc <= 22:
return 'BDS05' # airborne position, gnss-alt
return "BDS05" # airborne position, gnss-alt
if tc == 28:
return 'BDS61' # aircraft status
return "BDS61" # aircraft status
if tc == 29:
return 'BDS62' # target state and status
return "BDS62" # target state and status
if tc == 31:
return 'BDS65' # operational status
return "BDS65" # operational status
# For Comm-B replies
IS10 = bds10.is10(msg)
@@ -138,15 +153,27 @@ def infer(msg, mrar=False):
IS45 = bds45.is45(msg)
if mrar:
allbds = np.array(["BDS10", "BDS17", "BDS20", "BDS30", "BDS40",
"BDS44", "BDS45", "BDS50", "BDS60"])
allbds = np.array(
[
"BDS10",
"BDS17",
"BDS20",
"BDS30",
"BDS40",
"BDS44",
"BDS45",
"BDS50",
"BDS60",
]
)
mask = [IS10, IS17, IS20, IS30, IS40, IS44, IS45, IS50, IS60]
else:
allbds = np.array(["BDS10", "BDS17", "BDS20", "BDS30", "BDS40",
"BDS50", "BDS60"])
allbds = np.array(
["BDS10", "BDS17", "BDS20", "BDS30", "BDS40", "BDS50", "BDS60"]
)
mask = [IS10, IS17, IS20, IS30, IS40, IS50, IS60]
bds = ','.join(sorted(allbds[mask]))
bds = ",".join(sorted(allbds[mask]))
if len(bds) == 0:
return None

View File

@@ -24,6 +24,7 @@
from __future__ import absolute_import, print_function, division
from pyModeS.decoder import common
def airborne_position(msg0, msg1, t0, t1):
"""Decode airborn position from a pair of even and odd position message
@@ -40,6 +41,16 @@ def airborne_position(msg0, msg1, t0, t1):
mb0 = common.hex2bin(msg0)[32:]
mb1 = common.hex2bin(msg1)[32:]
oe0 = int(mb0[21])
oe1 = int(mb1[21])
if oe0 == 0 and oe1 == 1:
pass
elif oe0 == 1 and oe1 == 0:
mb0, mb1 = mb1, mb0
t0, t1 = t1, t0
else:
raise RuntimeError("Both even and odd CPR frames are required.")
# 131072 is 2^17, since CPR lat and lon are 17 bits each.
cprlat_even = common.bin2int(mb0[22:39]) / 131072.0
cprlon_even = common.bin2int(mb0[39:56]) / 131072.0
@@ -66,17 +77,17 @@ def airborne_position(msg0, msg1, t0, t1):
return None
# compute ni, longitude index m, and longitude
if (t0 > t1):
if t0 > t1:
lat = lat_even
nl = common.cprNL(lat)
ni = max(common.cprNL(lat)- 0, 1)
m = common.floor(cprlon_even * (nl-1) - cprlon_odd * nl + 0.5)
ni = max(common.cprNL(lat) - 0, 1)
m = common.floor(cprlon_even * (nl - 1) - cprlon_odd * nl + 0.5)
lon = (360.0 / ni) * (m % ni + cprlon_even)
else:
lat = lat_odd
nl = common.cprNL(lat)
ni = max(common.cprNL(lat) - 1, 1)
m = common.floor(cprlon_even * (nl-1) - cprlon_odd * nl + 0.5)
m = common.floor(cprlon_even * (nl - 1) - cprlon_odd * nl + 0.5)
lon = (360.0 / ni) * (m % ni + cprlon_odd)
if lon > 180:
@@ -100,17 +111,17 @@ def airborne_position_with_ref(msg, lat_ref, lon_ref):
(float, float): (latitude, longitude) of the aircraft
"""
mb = common.hex2bin(msg)[32:]
cprlat = common.bin2int(mb[22:39]) / 131072.0
cprlon = common.bin2int(mb[39:56]) / 131072.0
i = int(mb[21])
d_lat = 360.0/59 if i else 360.0/60
d_lat = 360.0 / 59 if i else 360.0 / 60
j = common.floor(lat_ref / d_lat) \
+ common.floor(0.5 + ((lat_ref % d_lat) / d_lat) - cprlat)
j = common.floor(lat_ref / d_lat) + common.floor(
0.5 + ((lat_ref % d_lat) / d_lat) - cprlat
)
lat = d_lat * (j + cprlat)
@@ -121,8 +132,9 @@ def airborne_position_with_ref(msg, lat_ref, lon_ref):
else:
d_lon = 360.0
m = common.floor(lon_ref / d_lon) \
+ common.floor(0.5 + ((lon_ref % d_lon) / d_lon) - cprlon)
m = common.floor(lon_ref / d_lon) + common.floor(
0.5 + ((lon_ref % d_lon) / d_lon) - cprlon
)
lon = d_lon * (m + cprlon)
@@ -141,7 +153,7 @@ def altitude(msg):
tc = common.typecode(msg)
if tc<9 or tc==19 or tc>22:
if tc < 9 or tc == 19 or tc > 22:
raise RuntimeError("%s: Not a airborn position message" % msg)
mb = common.hex2bin(msg)[32:]
@@ -150,7 +162,7 @@ def altitude(msg):
# barometric altitude
q = mb[15]
if q:
n = common.bin2int(mb[8:15]+mb[16:20])
n = common.bin2int(mb[8:15] + mb[16:20])
alt = n * 25 - 1000
else:
alt = None

View File

@@ -73,22 +73,25 @@ def surface_position(msg0, msg1, t0, t1, lat_ref, lon_ref):
return None
# compute ni, longitude index m, and longitude
if (t0 > t1):
if t0 > t1:
lat = lat_even
nl = common.cprNL(lat_even)
ni = max(common.cprNL(lat_even) - 0, 1)
m = common.floor(cprlon_even * (nl-1) - cprlon_odd * nl + 0.5)
m = common.floor(cprlon_even * (nl - 1) - cprlon_odd * nl + 0.5)
lon = (90.0 / ni) * (m % ni + cprlon_even)
else:
lat = lat_odd
nl = common.cprNL(lat_odd)
ni = max(common.cprNL(lat_odd) - 1, 1)
m = common.floor(cprlon_even * (nl-1) - cprlon_odd * nl + 0.5)
m = common.floor(cprlon_even * (nl - 1) - cprlon_odd * nl + 0.5)
lon = (90.0 / ni) * (m % ni + cprlon_odd)
# four possible longitude solutions
lons = [lon, lon + 90.0, lon + 180.0, lon + 270.0]
# make sure lons are between -180 and 180
lons = [(l + 180) % 360 - 180 for l in lons]
# the closest solution to receiver is the correct one
dls = [abs(lon_ref - l) for l in lons]
imin = min(range(4), key=dls.__getitem__)
@@ -112,17 +115,17 @@ def surface_position_with_ref(msg, lat_ref, lon_ref):
(float, float): (latitude, longitude) of the aircraft
"""
mb = common.hex2bin(msg)[32:]
cprlat = common.bin2int(mb[22:39]) / 131072.0
cprlon = common.bin2int(mb[39:56]) / 131072.0
i = int(mb[21])
d_lat = 90.0/59 if i else 90.0/60
d_lat = 90.0 / 59 if i else 90.0 / 60
j = common.floor(lat_ref / d_lat) \
+ common.floor(0.5 + ((lat_ref % d_lat) / d_lat) - cprlat)
j = common.floor(lat_ref / d_lat) + common.floor(
0.5 + ((lat_ref % d_lat) / d_lat) - cprlat
)
lat = d_lat * (j + cprlat)
@@ -133,8 +136,9 @@ def surface_position_with_ref(msg, lat_ref, lon_ref):
else:
d_lon = 90.0
m = common.floor(lon_ref / d_lon) \
+ common.floor(0.5 + ((lon_ref % d_lon) / d_lon) - cprlon)
m = common.floor(lon_ref / d_lon) + common.floor(
0.5 + ((lon_ref % d_lon) / d_lon) - cprlon
)
lon = d_lon * (m + cprlon)
@@ -184,11 +188,11 @@ def surface_velocity(msg, rtn_sources=False):
movs = [2, 9, 13, 39, 94, 109, 124]
kts = [0.125, 1, 2, 15, 70, 100, 175]
i = next(m[0] for m in enumerate(movs) if m[1] > mov)
step = (kts[i] - kts[i-1]) * 1.0 / (movs[i]-movs[i-1])
spd = kts[i-1] + (mov-movs[i-1]) * step
step = (kts[i] - kts[i - 1]) * 1.0 / (movs[i] - movs[i - 1])
spd = kts[i - 1] + (mov - movs[i - 1]) * step
spd = round(spd, 2)
if rtn_sources:
return spd, trk, 0, 'GS', 'true_north', None
return spd, trk, 0, "GS", "true_north", None
else:
return spd, trk, 0, 'GS'
return spd, trk, 0, "GS"

View File

@@ -23,6 +23,7 @@
from __future__ import absolute_import, print_function, division
from pyModeS.decoder import common
def category(msg):
"""Aircraft category number
@@ -37,7 +38,8 @@ def category(msg):
raise RuntimeError("%s: Not a identification message" % msg)
msgbin = common.hex2bin(msg)
return common.bin2int(msgbin[5:8])
mebin = msgbin[32:87]
return common.bin2int(mebin[5:8])
def callsign(msg):
@@ -53,11 +55,11 @@ def callsign(msg):
if common.typecode(msg) < 1 or common.typecode(msg) > 4:
raise RuntimeError("%s: Not a identification message" % msg)
chars = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ#####_###############0123456789######'
chars = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ#####_###############0123456789######"
msgbin = common.hex2bin(msg)
csbin = msgbin[40:96]
cs = ''
cs = ""
cs += chars[common.bin2int(csbin[0:6])]
cs += chars[common.bin2int(csbin[6:12])]
cs += chars[common.bin2int(csbin[12:18])]
@@ -69,5 +71,5 @@ def callsign(msg):
# clean string, remove spaces and marks, if any.
# cs = cs.replace('_', '')
cs = cs.replace('#', '')
cs = cs.replace("#", "")
return cs

View File

@@ -57,32 +57,32 @@ def airborne_velocity(msg, rtn_sources=False):
return None
if subtype in (1, 2):
v_ew_sign = -1 if mb[13]=='1' else 1
v_ew = common.bin2int(mb[14:24]) - 1 # east-west velocity
if subtype == 2: # Supersonic
v_ew_sign = -1 if mb[13] == "1" else 1
v_ew = common.bin2int(mb[14:24]) - 1 # east-west velocity
if subtype == 2: # Supersonic
v_ew *= 4
v_ns_sign = -1 if mb[24]=='1' else 1
v_ns = common.bin2int(mb[25:35]) - 1 # north-south velocity
if subtype == 2: # Supersonic
v_ns_sign = -1 if mb[24] == "1" else 1
v_ns = common.bin2int(mb[25:35]) - 1 # north-south velocity
if subtype == 2: # Supersonic
v_ns *= 4
v_we = v_ew_sign * v_ew
v_sn = v_ns_sign * v_ns
spd = math.sqrt(v_sn*v_sn + v_we*v_we) # unit in kts
spd = math.sqrt(v_sn * v_sn + v_we * v_we) # unit in kts
spd = int(spd)
trk = math.atan2(v_we, v_sn)
trk = math.degrees(trk) # convert to degrees
trk = trk if trk >= 0 else trk + 360 # no negative val
trk = math.degrees(trk) # convert to degrees
trk = trk if trk >= 0 else trk + 360 # no negative val
tag = 'GS'
tag = "GS"
trk_or_hdg = round(trk, 2)
dir_type = 'true_north'
dir_type = "true_north"
else:
if mb[13] == '0':
if mb[13] == "0":
hdg = None
else:
hdg = common.bin2int(mb[14:24]) / 1024.0 * 360.0
@@ -91,27 +91,27 @@ def airborne_velocity(msg, rtn_sources=False):
trk_or_hdg = hdg
spd = common.bin2int(mb[25:35])
spd = None if spd==0 else spd-1
if subtype == 4: # Supersonic
spd = None if spd == 0 else spd - 1
if subtype == 4: # Supersonic
spd *= 4
if mb[24]=='0':
tag = 'IAS'
if mb[24] == "0":
tag = "IAS"
else:
tag = 'TAS'
dir_type = 'mag_north'
tag = "TAS"
vr_source = 'GNSS' if mb[35]=='0' else 'Baro'
vr_sign = -1 if mb[36]=='1' else 1
dir_type = "mag_north"
vr_source = "GNSS" if mb[35] == "0" else "Baro"
vr_sign = -1 if mb[36] == "1" else 1
vr = common.bin2int(mb[37:46])
rocd = None if vr==0 else int(vr_sign*(vr-1)*64)
rocd = None if vr == 0 else int(vr_sign * (vr - 1) * 64)
if rtn_sources:
return spd, trk_or_hdg, rocd, tag, dir_type, vr_source
else:
return spd, trk_or_hdg, rocd, tag
def altitude_diff(msg):
"""Decode the differece between GNSS and barometric altitude
@@ -135,4 +135,4 @@ def altitude_diff(msg):
if value == 0 or value == 127:
return None
else:
return sign * (value - 1) * 25 # in ft.
return sign * (value - 1) * 25 # in ft.

View File

@@ -21,6 +21,7 @@
from __future__ import absolute_import, print_function, division
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros
def is10(msg):
"""Check if a message is likely to be BDS code 1,0
@@ -37,7 +38,7 @@ def is10(msg):
d = hex2bin(data(msg))
# first 8 bits must be 0x10
if d[0:8] != '00010000':
if d[0:8] != "00010000":
return False
# bit 10 to 14 are reserved
@@ -45,13 +46,14 @@ def is10(msg):
return False
# overlay capabilty conflict
if d[14] == '1' and bin2int(d[16:23]) < 5:
if d[14] == "1" and bin2int(d[16:23]) < 5:
return False
if d[14] == '0' and bin2int(d[16:23]) > 4:
if d[14] == "0" and bin2int(d[16:23]) > 4:
return False
return True
def ovc10(msg):
"""Return the overlay control capability

View File

@@ -50,11 +50,12 @@ def is17(msg):
# return False
# at least you can respond who you are
if 'BDS20' not in caps:
if "BDS20" not in caps:
return False
return True
def cap17(msg):
"""Extract capacities from BDS 1,7 message
@@ -64,12 +65,39 @@ def cap17(msg):
Returns:
list: list of suport BDS codes
"""
allbds = ['05', '06', '07', '08', '09', '0A', '20', '21', '40', '41',
'42', '43', '44', '45', '48', '50', '51', '52', '53', '54',
'55', '56', '5F', '60', 'NA', 'NA', 'E1', 'E2']
allbds = [
"05",
"06",
"07",
"08",
"09",
"0A",
"20",
"21",
"40",
"41",
"42",
"43",
"44",
"45",
"48",
"50",
"51",
"52",
"53",
"54",
"55",
"56",
"5F",
"60",
"NA",
"NA",
"E1",
"E2",
]
d = hex2bin(data(msg))
idx = [i for i, v in enumerate(d[:28]) if v=='1']
capacity = ['BDS'+allbds[i] for i in idx if allbds[i] is not 'NA']
idx = [i for i, v in enumerate(d[:28]) if v == "1"]
capacity = ["BDS" + allbds[i] for i in idx if allbds[i] is not "NA"]
return capacity

View File

@@ -21,6 +21,7 @@
from __future__ import absolute_import, print_function, division
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros
def is20(msg):
"""Check if a message is likely to be BDS code 2,0
@@ -36,12 +37,12 @@ def is20(msg):
d = hex2bin(data(msg))
if d[0:8] != '00100000':
if d[0:8] != "00100000":
return False
cs = cs20(msg)
if '#' in cs:
if "#" in cs:
return False
return True
@@ -56,11 +57,11 @@ def cs20(msg):
Returns:
string: callsign, max. 8 chars
"""
chars = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ#####_###############0123456789######'
chars = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ#####_###############0123456789######"
d = hex2bin(data(msg))
cs = ''
cs = ""
cs += chars[bin2int(d[8:14])]
cs += chars[bin2int(d[14:20])]
cs += chars[bin2int(d[20:26])]

View File

@@ -21,6 +21,7 @@
from __future__ import absolute_import, print_function, division
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros
def is30(msg):
"""Check if a message is likely to be BDS code 2,0
@@ -36,11 +37,11 @@ def is30(msg):
d = hex2bin(data(msg))
if d[0:8] != '00110000':
if d[0:8] != "00110000":
return False
# threat type 3 not assigned
if d[28:30] == '11':
if d[28:30] == "11":
return False
# reserved for ACAS III, in far future

View File

@@ -121,7 +121,6 @@ def p40baro(msg):
def alt40mcp(msg):
warnings.simplefilter("once", DeprecationWarning)
warnings.warn(
"alt40mcp() has been renamed to selalt40mcp(). It will be removed in the future.",
DeprecationWarning,
@@ -130,7 +129,6 @@ def alt40mcp(msg):
def alt40fms(msg):
warnings.simplefilter("once", DeprecationWarning)
warnings.warn(
"alt40fms() has been renamed to selalt40fms(). It will be removed in the future.",
DeprecationWarning,

View File

@@ -19,13 +19,7 @@
# ------------------------------------------
from __future__ import absolute_import, print_function, division
from pyModeS.decoder.common import (
hex2bin,
bin2int,
data,
allzeros,
wrongstatus,
)
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros, wrongstatus
def is44(msg):

View File

@@ -87,7 +87,7 @@ def turb45(msg):
"""
d = hex2bin(data(msg))
if d[0] == '0':
if d[0] == "0":
return None
turb = bin2int(d[1:3])
@@ -105,7 +105,7 @@ def ws45(msg):
"""
d = hex2bin(data(msg))
if d[3] == '0':
if d[3] == "0":
return None
ws = bin2int(d[4:6])
@@ -123,7 +123,7 @@ def mb45(msg):
"""
d = hex2bin(data(msg))
if d[6] == '0':
if d[6] == "0":
return None
mb = bin2int(d[7:9])
@@ -141,7 +141,7 @@ def ic45(msg):
"""
d = hex2bin(data(msg))
if d[9] == '0':
if d[9] == "0":
return None
ic = bin2int(d[10:12])
@@ -159,7 +159,7 @@ def wv45(msg):
"""
d = hex2bin(data(msg))
if d[12] == '0':
if d[12] == "0":
return None
ws = bin2int(d[13:15])
@@ -184,7 +184,7 @@ def temp45(msg):
if sign:
value = value - 512
temp = value * 0.25 # celsius
temp = value * 0.25 # celsius
temp = round(temp, 1)
return temp
@@ -201,9 +201,9 @@ def p45(msg):
"""
d = hex2bin(data(msg))
if d[26] == '0':
if d[26] == "0":
return None
p = bin2int(d[27:38]) # hPa
p = bin2int(d[27:38]) # hPa
return p
@@ -218,7 +218,7 @@ def rh45(msg):
"""
d = hex2bin(data(msg))
if d[38] == '0':
if d[38] == "0":
return None
rh = bin2int(d[39:51]) * 16
return rh

View File

@@ -85,16 +85,16 @@ def roll50(msg):
"""
d = hex2bin(data(msg))
if d[0] == '0':
if d[0] == "0":
return None
sign = int(d[1]) # 1 -> left wing down
sign = int(d[1]) # 1 -> left wing down
value = bin2int(d[2:11])
if sign:
value = value - 512
angle = value * 45.0 / 256.0 # degree
angle = value * 45.0 / 256.0 # degree
return round(angle, 1)
@@ -109,10 +109,10 @@ def trk50(msg):
"""
d = hex2bin(data(msg))
if d[11] == '0':
if d[11] == "0":
return None
sign = int(d[12]) # 1 -> west
sign = int(d[12]) # 1 -> west
value = bin2int(d[13:23])
if sign:
@@ -138,10 +138,10 @@ def gs50(msg):
"""
d = hex2bin(data(msg))
if d[23] == '0':
if d[23] == "0":
return None
spd = bin2int(d[24:34]) * 2 # kts
spd = bin2int(d[24:34]) * 2 # kts
return spd
@@ -156,18 +156,18 @@ def rtrk50(msg):
"""
d = hex2bin(data(msg))
if d[34] == '0':
if d[34] == "0":
return None
if d[36:45] == "111111111":
return None
sign = int(d[35]) # 1 -> negative value, two's complement
sign = int(d[35]) # 1 -> negative value, two's complement
value = bin2int(d[36:45])
if sign:
value = value - 512
angle = value * 8.0 / 256.0 # degree / sec
angle = value * 8.0 / 256.0 # degree / sec
return round(angle, 3)
@@ -182,8 +182,8 @@ def tas50(msg):
"""
d = hex2bin(data(msg))
if d[45] == '0':
if d[45] == "0":
return None
tas = bin2int(d[46:56]) * 2 # kts
tas = bin2int(d[46:56]) * 2 # kts
return tas

View File

@@ -85,16 +85,16 @@ def hdg53(msg):
"""
d = hex2bin(data(msg))
if d[0] == '0':
if d[0] == "0":
return None
sign = int(d[1]) # 1 -> west
sign = int(d[1]) # 1 -> west
value = bin2int(d[2:12])
if sign:
value = value - 1024
hdg = value * 90.0 / 512.0 # degree
hdg = value * 90.0 / 512.0 # degree
# convert from [-180, 180] to [0, 360]
if hdg < 0:
@@ -114,10 +114,10 @@ def ias53(msg):
"""
d = hex2bin(data(msg))
if d[12] == '0':
if d[12] == "0":
return None
ias = bin2int(d[13:23]) # knots
ias = bin2int(d[13:23]) # knots
return ias
@@ -132,7 +132,7 @@ def mach53(msg):
"""
d = hex2bin(data(msg))
if d[23] == '0':
if d[23] == "0":
return None
mach = bin2int(d[24:33]) * 0.008
@@ -150,12 +150,13 @@ def tas53(msg):
"""
d = hex2bin(data(msg))
if d[33] == '0':
if d[33] == "0":
return None
tas = bin2int(d[34:46]) * 0.5 # kts
tas = bin2int(d[34:46]) * 0.5 # kts
return round(tas, 1)
def vr53(msg):
"""Vertical rate
@@ -167,16 +168,16 @@ def vr53(msg):
"""
d = hex2bin(data(msg))
if d[46] == '0':
if d[46] == "0":
return None
sign = int(d[47]) # 1 -> negative value, two's complement
sign = int(d[47]) # 1 -> negative value, two's complement
value = bin2int(d[48:56])
if value == 0 or value == 255: # all zeros or all ones
return 0
value = value - 256 if sign else value
roc = value * 64 # feet/min
roc = value * 64 # feet/min
return roc

View File

@@ -21,6 +21,7 @@
from __future__ import absolute_import, print_function, division
from pyModeS.decoder.common import hex2bin, bin2int, data, allzeros, wrongstatus
def is60(msg):
"""Check if a message is likely to be BDS code 6,0
@@ -83,10 +84,10 @@ def hdg60(msg):
"""
d = hex2bin(data(msg))
if d[0] == '0':
if d[0] == "0":
return None
sign = int(d[1]) # 1 -> west
sign = int(d[1]) # 1 -> west
value = bin2int(d[2:12])
if sign:
@@ -112,10 +113,10 @@ def ias60(msg):
"""
d = hex2bin(data(msg))
if d[12] == '0':
if d[12] == "0":
return None
ias = bin2int(d[13:23]) # kts
ias = bin2int(d[13:23]) # kts
return ias
@@ -130,7 +131,7 @@ def mach60(msg):
"""
d = hex2bin(data(msg))
if d[23] == '0':
if d[23] == "0":
return None
mach = bin2int(d[24:34]) * 2.048 / 512.0
@@ -148,10 +149,10 @@ def vr60baro(msg):
"""
d = hex2bin(data(msg))
if d[34] == '0':
if d[34] == "0":
return None
sign = int(d[35]) # 1 -> negative value, two's complement
sign = int(d[35]) # 1 -> negative value, two's complement
value = bin2int(d[36:45])
if value == 0 or value == 511: # all zeros or all ones
@@ -159,7 +160,7 @@ def vr60baro(msg):
value = value - 512 if sign else value
roc = value * 32 # feet/min
roc = value * 32 # feet/min
return roc
@@ -174,10 +175,10 @@ def vr60ins(msg):
"""
d = hex2bin(data(msg))
if d[45] == '0':
if d[45] == "0":
return None
sign = int(d[46]) # 1 -> negative value, two's complement
sign = int(d[46]) # 1 -> negative value, two's complement
value = bin2int(d[47:56])
if value == 0 or value == 511: # all zeros or all ones
@@ -185,5 +186,5 @@ def vr60ins(msg):
value = value - 512 if sign else value
roc = value * 32 # feet/min
roc = value * 32 # feet/min
return roc

View File

@@ -2,6 +2,7 @@ from __future__ import absolute_import, print_function, division
import numpy as np
from textwrap import wrap
def hex2bin(hexstr):
"""Convert a hexdecimal string to binary string, with zero fillings."""
num_of_bits = len(hexstr) * 4
@@ -16,7 +17,8 @@ def hex2int(hexstr):
def int2hex(n):
"""Convert a integer to hexadecimal string."""
return hex(n)[2:].rjust(6, '0').upper()
# strip 'L' for python 2
return hex(n)[2:].rjust(6, "0").upper().rstrip("L")
def bin2int(binstr):
@@ -36,7 +38,7 @@ def bin2np(binstr):
def np2bin(npbin):
"""Convert a binary numpy array to string."""
return np.array2string(npbin, separator='')[1:-1]
return np.array2string(npbin, separator="")[1:-1]
def df(msg):
@@ -59,10 +61,7 @@ def crc(msg, encode=False):
"""
# the CRC generator
G = [
int("11111111", 2), int("11111010", 2),
int("00000100", 2), int("10000000", 2)
]
G = [int("11111111", 2), int("11111010", 2), int("00000100", 2), int("10000000", 2)]
if encode:
msg = msg[:-6] + "000000"
@@ -71,16 +70,22 @@ def crc(msg, encode=False):
msgbin_split = wrap(msgbin, 8)
mbytes = list(map(bin2int, msgbin_split))
for ibyte in range(len(mbytes)-3):
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)))
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))
)
result = (mbytes[-3] << 16) | (mbytes[-2] << 8) | mbytes[-1]
@@ -90,7 +95,9 @@ def crc(msg, encode=False):
def crc_legacy(msg, encode=False):
"""Mode-S Cyclic Redundancy Check. (Legacy code, 2x slow)."""
# the polynominal generattor code for CRC [1111111111111010000001001]
generator = np.array([1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,1,0,0,1])
generator = np.array(
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1]
)
ng = len(generator)
msgnpbin = bin2np(hex2bin(msg))
@@ -99,12 +106,12 @@ def crc_legacy(msg, encode=False):
msgnpbin[-24:] = [0] * 24
# loop all bits, except last 24 piraty bits
for i in range(len(msgnpbin)-24):
for i in range(len(msgnpbin) - 24):
if msgnpbin[i] == 0:
continue
# perform XOR, when 1
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
reminder = bin2int(np2bin(msgnpbin[-24:]))
@@ -140,7 +147,7 @@ def icao(msg):
elif DF in (0, 4, 5, 16, 20, 21):
c0 = crc(msg, encode=True)
c1 = hex2int(msg[-6:])
addr = '%06X' % (c0 ^ c1)
addr = "%06X" % (c0 ^ c1)
else:
addr = None
@@ -149,23 +156,33 @@ def icao(msg):
def is_icao_assigned(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):
if (icao is None) or (not isinstance(icao, str)) or (len(icao) != 6):
return False
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
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
def typecode(msg):
"""Type code of ADS-B message
@@ -197,7 +214,7 @@ def cprNL(lat):
nz = 15
a = 1 - np.cos(np.pi / (2 * nz))
b = np.cos(np.pi / 180.0 * abs(lat)) ** 2
nl = 2 * np.pi / (np.arccos(1 - a/b))
nl = 2 * np.pi / (np.arccos(1 - a / b))
NL = floor(nl)
return NL
@@ -234,10 +251,10 @@ def idcode(msg):
B4 = mbin[30]
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)
byte1 = int(A4 + A2 + A1, 2)
byte2 = int(B4 + B2 + B1, 2)
byte3 = int(C4 + C2 + C1, 2)
byte4 = int(D4 + D2 + D1, 2)
return str(byte1) + str(byte2) + str(byte3) + str(byte4)
@@ -261,15 +278,14 @@ def altcode(msg):
# Altitude code, bit 20-32
mbin = hex2bin(msg)
mbit = mbin[25] # M bit: 26
qbit = mbin[27] # Q bit: 28
mbit = mbin[25] # M bit: 26
qbit = mbin[27] # Q bit: 28
if mbit == '0': # unit in ft
if qbit == '1': # 25ft interval
if mbit == "0": # unit in ft
if qbit == "1": # 25ft interval
vbin = mbin[19:25] + mbin[26] + mbin[28:32]
alt = bin2int(vbin) * 25 - 1000
if qbit == '0': # 100ft interval, above 50175ft
if qbit == "0": # 100ft interval, above 50175ft
C1 = mbin[19]
A1 = mbin[20]
C2 = mbin[21]
@@ -284,10 +300,10 @@ def altcode(msg):
B4 = mbin[30]
D4 = mbin[31]
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)
if mbit == '1': # unit in meter
if mbit == "1": # unit in meter
vbin = mbin[19:25] + mbin[26:31]
alt = int(bin2int(vbin) * 3.28084) # convert to ft
@@ -308,20 +324,20 @@ def gray2alt(codestr):
if n100 == 7:
n100 = 5
if n500%2:
if n500 % 2:
n100 = 6 - n100
alt = (n500*500 + n100*100) - 1300
alt = (n500 * 500 + n100 * 100) - 1300
return alt
def gray2int(graystr):
"""Convert greycode to binary."""
num = bin2int(graystr)
num ^= (num >> 8)
num ^= (num >> 4)
num ^= (num >> 2)
num ^= (num >> 1)
num ^= num >> 8
num ^= num >> 4
num ^= num >> 2
num ^= num >> 1
return num
@@ -355,8 +371,8 @@ def wrongstatus(data, sb, msb, lsb):
"""
# status bit, most significant bit, least significant bit
status = int(data[sb-1])
value = bin2int(data[msb-1:lsb])
status = int(data[sb - 1])
value = bin2int(data[msb - 1 : lsb])
if not status:
if value != 0:

View File

@@ -17,13 +17,20 @@ from pyModeS.decoder.bds.bds50 import *
from pyModeS.decoder.bds.bds60 import *
from pyModeS.decoder.bds import infer
warnings.simplefilter('once', DeprecationWarning)
warnings.warn("pms.ehs module is deprecated. Please use pms.commb instead.", DeprecationWarning)
warnings.simplefilter("once", DeprecationWarning)
warnings.warn(
"pms.ehs module is deprecated. Please use pms.commb instead.", DeprecationWarning
)
def BDS(msg):
warnings.warn("pms.ehs.BDS() is deprecated, use pms.bds.infer() instead.", DeprecationWarning)
warnings.warn(
"pms.ehs.BDS() is deprecated, use pms.bds.infer() instead.", DeprecationWarning
)
return infer(msg)
def icao(msg):
from pyModeS.decoder.common import icao
return icao(msg)

View File

@@ -18,5 +18,8 @@ from pyModeS.decoder.bds.bds20 import *
from pyModeS.decoder.bds.bds30 import *
import warnings
warnings.simplefilter('once', DeprecationWarning)
warnings.warn("pms.els module is deprecated. Please use pms.commb instead.", DeprecationWarning)
warnings.simplefilter("once", DeprecationWarning)
warnings.warn(
"pms.els module is deprecated. Please use pms.commb instead.", DeprecationWarning
)

View File

@@ -6,120 +6,147 @@ See source code at: https://github.com/junzis/pyModeS/blob/master/pyModeS/decode
NA = None
TC_NUCp_lookup = {
0:0, 5:9, 6:8, 7:7, 8:6,
9:9, 10:8, 11:7, 12:6, 13:5, 14:4, 15:3, 16:2, 17:1, 18:0,
20:9, 21:8, 22:0
0: 0,
5: 9,
6: 8,
7: 7,
8: 6,
9: 9,
10: 8,
11: 7,
12: 6,
13: 5,
14: 4,
15: 3,
16: 2,
17: 1,
18: 0,
20: 9,
21: 8,
22: 0,
}
TC_NICv1_lookup = {
5:11, 6:10, 7:9, 8:0,
9:11, 10:10, 11:{1:9, 0:8}, 12:7, 13:6, 14:5, 15:4, 16:{1:3, 0:2}, 17:1, 18:0,
20:11, 21:10, 22:0
5: 11,
6: 10,
7: 9,
8: 0,
9: 11,
10: 10,
11: {1: 9, 0: 8},
12: 7,
13: 6,
14: 5,
15: 4,
16: {1: 3, 0: 2},
17: 1,
18: 0,
20: 11,
21: 10,
22: 0,
}
TC_NICv2_lookup = {
5:11, 6:10, 7:{2:9, 0:8}, 8:{3:7, 2:6, 1:6, 0:0},
9:11, 10:10, 11:{3:9, 0:8}, 12:7, 13:6, 14:5, 15:4, 16:{3:3, 0:2}, 17:1, 18:0,
20:11, 21:10, 22:0
5: 11,
6: 10,
7: {2: 9, 0: 8},
8: {3: 7, 2: 6, 1: 6, 0: 0},
9: 11,
10: 10,
11: {3: 9, 0: 8},
12: 7,
13: 6,
14: 5,
15: 4,
16: {3: 3, 0: 2},
17: 1,
18: 0,
20: 11,
21: 10,
22: 0,
}
NUCp = {
9: {'HPL':7.5, 'RCu':3, 'RCv':4},
8: {'HPL':25, 'RCu':10, 'RCv':15},
7: {'HPL':185, 'RCu':93, 'RCv':NA},
6: {'HPL':370, 'RCu':185, 'RCv':NA},
5: {'HPL':926, 'RCu':463, 'RCv':NA},
4: {'HPL':1852, 'RCu':926, 'RCv':NA},
3: {'HPL':3704, 'RCu':1852, 'RCv':NA},
2: {'HPL':18520, 'RCu':9260, 'RCv':NA},
1: {'HPL':37040, 'RCu':18520, 'RCv':NA},
0: {'HPL':NA, 'RCu':NA, 'RCv':NA},
9: {"HPL": 7.5, "RCu": 3, "RCv": 4},
8: {"HPL": 25, "RCu": 10, "RCv": 15},
7: {"HPL": 185, "RCu": 93, "RCv": NA},
6: {"HPL": 370, "RCu": 185, "RCv": NA},
5: {"HPL": 926, "RCu": 463, "RCv": NA},
4: {"HPL": 1852, "RCu": 926, "RCv": NA},
3: {"HPL": 3704, "RCu": 1852, "RCv": NA},
2: {"HPL": 18520, "RCu": 9260, "RCv": NA},
1: {"HPL": 37040, "RCu": 18520, "RCv": NA},
0: {"HPL": NA, "RCu": NA, "RCv": NA},
}
NUCv = {
0: {'HVE':NA, 'VVE':NA},
1: {'HVE':10, 'VVE':15.2},
2: {'HVE':3, 'VVE':4.5},
3: {'HVE':1, 'VVE':1.5},
4: {'HVE':0.3, 'VVE':0.46},
0: {"HVE": NA, "VVE": NA},
1: {"HVE": 10, "VVE": 15.2},
2: {"HVE": 3, "VVE": 4.5},
3: {"HVE": 1, "VVE": 1.5},
4: {"HVE": 0.3, "VVE": 0.46},
}
NACp = {
11: {'EPU': 3, 'VEPU': 4},
10: {'EPU': 10, 'VEPU': 15},
9: {'EPU': 30, 'VEPU': 45},
8: {'EPU': 93, 'VEPU': NA},
7: {'EPU': 185, 'VEPU': NA},
6: {'EPU': 556, 'VEPU': NA},
5: {'EPU': 926, 'VEPU': NA},
4: {'EPU': 1852, 'VEPU': NA},
3: {'EPU': 3704, 'VEPU': NA},
2: {'EPU': 7408, 'VEPU': NA},
1: {'EPU': 18520, 'VEPU': NA},
0: {'EPU': NA, 'VEPU': NA},
11: {"EPU": 3, "VEPU": 4},
10: {"EPU": 10, "VEPU": 15},
9: {"EPU": 30, "VEPU": 45},
8: {"EPU": 93, "VEPU": NA},
7: {"EPU": 185, "VEPU": NA},
6: {"EPU": 556, "VEPU": NA},
5: {"EPU": 926, "VEPU": NA},
4: {"EPU": 1852, "VEPU": NA},
3: {"EPU": 3704, "VEPU": NA},
2: {"EPU": 7408, "VEPU": NA},
1: {"EPU": 18520, "VEPU": NA},
0: {"EPU": NA, "VEPU": NA},
}
NACv = {
0: {'HFOMr':NA, 'VFOMr':NA},
1: {'HFOMr':10, 'VFOMr':15.2},
2: {'HFOMr':3, 'VFOMr':4.5},
3: {'HFOMr':1, 'VFOMr':1.5},
4: {'HFOMr':0.3, 'VFOMr':0.46},
0: {"HFOMr": NA, "VFOMr": NA},
1: {"HFOMr": 10, "VFOMr": 15.2},
2: {"HFOMr": 3, "VFOMr": 4.5},
3: {"HFOMr": 1, "VFOMr": 1.5},
4: {"HFOMr": 0.3, "VFOMr": 0.46},
}
SIL = {
3: {'PE_RCu': 1e-7, 'PE_VPL': 2e-7},
2: {'PE_RCu': 1e-5, 'PE_VPL': 1e-5},
1: {'PE_RCu': 1e-3, 'PE_VPL': 1e-3},
0: {'PE_RCu': NA, 'PE_VPL': NA},
3: {"PE_RCu": 1e-7, "PE_VPL": 2e-7},
2: {"PE_RCu": 1e-5, "PE_VPL": 1e-5},
1: {"PE_RCu": 1e-3, "PE_VPL": 1e-3},
0: {"PE_RCu": NA, "PE_VPL": NA},
}
NICv1 = {
# NIC is used as the index at second Level
11: {0: {'Rc': 7.5, 'VPL': 11}},
10: {0: {'Rc': 25, 'VPL': 37.5}},
9: {1: {'Rc': 75, 'VPL': 112}},
8: {0: {'Rc': 185, 'VPL': NA}},
7: {0: {'Rc': 370, 'VPL': NA}},
6: {
0: {'Rc': 926, 'VPL': NA},
1: {'Rc': 1111, 'VPL': NA},
},
5: {0: {'Rc': 1852, 'VPL': NA}},
4: {0: {'Rc': 3702, 'VPL': NA}},
3: {1: {'Rc': 7408, 'VPL': NA}},
2: {0: {'Rc': 14008, 'VPL': NA}},
1: {0: {'Rc': 37000, 'VPL': NA}},
0: {0: {'Rc': NA, 'VPL': NA}},
11: {0: {"Rc": 7.5, "VPL": 11}},
10: {0: {"Rc": 25, "VPL": 37.5}},
9: {1: {"Rc": 75, "VPL": 112}},
8: {0: {"Rc": 185, "VPL": NA}},
7: {0: {"Rc": 370, "VPL": NA}},
6: {0: {"Rc": 926, "VPL": NA}, 1: {"Rc": 1111, "VPL": NA}},
5: {0: {"Rc": 1852, "VPL": NA}},
4: {0: {"Rc": 3702, "VPL": NA}},
3: {1: {"Rc": 7408, "VPL": NA}},
2: {0: {"Rc": 14008, "VPL": NA}},
1: {0: {"Rc": 37000, "VPL": NA}},
0: {0: {"Rc": NA, "VPL": NA}},
}
NICv2 = {
# Decimal value of [NICa NICb/NICc] is used as the index at second Level
11: {0: {'Rc': 7.5}},
10: {0: {'Rc': 25}},
9: {
2: {'Rc': 75},
3: {'Rc': 75},
},
8: {0: {'Rc': 185}},
7: {
0: {'Rc': 370},
3: {'Rc': 370},
},
6: {
0: {'Rc': 926},
1: {'Rc': 556},
2: {'Rc': 556},
3: {'Rc': 1111},
},
5: {0: {'Rc': 1852}},
4: {0: {'Rc': 3702}},
3: {3: {'Rc': 7408}},
2: {0: {'Rc': 14008}},
1: {0: {'Rc': 37000}},
0: {0: {'Rc': NA}},
11: {0: {"Rc": 7.5}},
10: {0: {"Rc": 25}},
9: {2: {"Rc": 75}, 3: {"Rc": 75}},
8: {0: {"Rc": 185}},
7: {0: {"Rc": 370}, 3: {"Rc": 370}},
6: {0: {"Rc": 926}, 1: {"Rc": 556}, 2: {"Rc": 556}, 3: {"Rc": 1111}},
5: {0: {"Rc": 1852}},
4: {0: {"Rc": 3702}},
3: {3: {"Rc": 7408}},
2: {0: {"Rc": 14008}},
1: {0: {"Rc": 37000}},
0: {0: {"Rc": NA}},
}

View File

@@ -30,31 +30,31 @@ Speed conversion at altitude H[m] in ISA
import numpy as np
"""Aero and geo Constants """
kts = 0.514444 # knot -> m/s
ft = 0.3048 # ft -> m
fpm = 0.00508 # ft/min -> m/s
inch = 0.0254 # inch -> m
sqft = 0.09290304 # 1 square foot
nm = 1852. # nautical mile -> m
lbs = 0.453592 # pound -> kg
g0 = 9.80665 # m/s2, Sea level gravity constant
R = 287.05287 # m2/(s2 x K), gas constant, sea level ISA
p0 = 101325. # Pa, air pressure, sea level ISA
rho0 = 1.225 # kg/m3, air density, sea level ISA
T0 = 288.15 # K, temperature, sea level ISA
gamma = 1.40 # cp/cv for air
gamma1 = 0.2 # (gamma-1)/2 for air
gamma2 = 3.5 # gamma/(gamma-1) for air
beta = -0.0065 # [K/m] ISA temp gradient below tropopause
r_earth = 6371000. # m, average earth radius
a0 = 340.293988 # m/s, sea level speed of sound ISA, sqrt(gamma*R*T0)
kts = 0.514444 # knot -> m/s
ft = 0.3048 # ft -> m
fpm = 0.00508 # ft/min -> m/s
inch = 0.0254 # inch -> m
sqft = 0.09290304 # 1 square foot
nm = 1852.0 # nautical mile -> m
lbs = 0.453592 # pound -> kg
g0 = 9.80665 # m/s2, Sea level gravity constant
R = 287.05287 # m2/(s2 x K), gas constant, sea level ISA
p0 = 101325.0 # Pa, air pressure, sea level ISA
rho0 = 1.225 # kg/m3, air density, sea level ISA
T0 = 288.15 # K, temperature, sea level ISA
gamma = 1.40 # cp/cv for air
gamma1 = 0.2 # (gamma-1)/2 for air
gamma2 = 3.5 # gamma/(gamma-1) for air
beta = -0.0065 # [K/m] ISA temp gradient below tropopause
r_earth = 6371000.0 # m, average earth radius
a0 = 340.293988 # m/s, sea level speed of sound ISA, sqrt(gamma*R*T0)
def atmos(H):
# H in metres
T = np.maximum(288.15 - 0.0065 * H, 216.65)
rhotrop = 1.225 * (T / 288.15)**4.256848030018761
dhstrat = np.maximum(0., H - 11000.0)
rhotrop = 1.225 * (T / 288.15) ** 4.256848030018761
dhstrat = np.maximum(0.0, H - 11000.0)
rho = rhotrop * np.exp(-dhstrat / 6341.552161)
p = rho * R * T
return p, rho, T
@@ -101,11 +101,13 @@ def distance(lat1, lon1, lat2, lon2, H=0):
theta1 = np.radians(lon1)
theta2 = np.radians(lon2)
cos = np.sin(phi1) * np.sin(phi2) * np.cos(theta1 - theta2) + np.cos(phi1) * np.cos(phi2)
cos = np.where(cos>1, 1, cos)
cos = np.sin(phi1) * np.sin(phi2) * np.cos(theta1 - theta2) + np.cos(phi1) * np.cos(
phi2
)
cos = np.where(cos > 1, 1, cos)
arc = np.arccos(cos)
dist = arc * (r_earth + H) # meters, radius of earth
dist = arc * (r_earth + H) # meters, radius of earth
return dist
@@ -114,9 +116,8 @@ def bearing(lat1, lon1, lat2, lon2):
lon1 = np.radians(lon1)
lat2 = np.radians(lat2)
lon2 = np.radians(lon2)
x = np.sin(lon2-lon1) * np.cos(lat2)
y = np.cos(lat1) * np.sin(lat2) \
- np.sin(lat1) * np.cos(lat2) * np.cos(lon2-lon1)
x = np.sin(lon2 - lon1) * np.cos(lat2)
y = np.cos(lat1) * np.sin(lat2) - np.sin(lat1) * np.cos(lat2) * np.cos(lon2 - lon1)
initial_bearing = np.arctan2(x, y)
initial_bearing = np.degrees(initial_bearing)
bearing = (initial_bearing + 360) % 360
@@ -129,44 +130,44 @@ def bearing(lat1, lon1, lat2, lon2):
def tas2mach(Vtas, H):
"""True Airspeed to Mach number"""
a = vsound(H)
Mach = Vtas/a
Mach = Vtas / a
return Mach
def mach2tas(Mach, H):
"""Mach number to True Airspeed"""
a = vsound(H)
Vtas = Mach*a
Vtas = Mach * a
return Vtas
def eas2tas(Veas, H):
"""Equivalent Airspeed to True Airspeed"""
rho = density(H)
Vtas = Veas * np.sqrt(rho0/rho)
Vtas = Veas * np.sqrt(rho0 / rho)
return Vtas
def tas2eas(Vtas, H):
"""True Airspeed to Equivalent Airspeed"""
rho = density(H)
Veas = Vtas * np.sqrt(rho/rho0)
Veas = Vtas * np.sqrt(rho / rho0)
return Veas
def cas2tas(Vcas, H):
"""Calibrated Airspeed to True Airspeed"""
p, rho, T = atmos(H)
qdyn = p0*((1.+rho0*Vcas*Vcas/(7.*p0))**3.5-1.)
Vtas = np.sqrt(7.*p/rho*((1.+qdyn/p)**(2./7.)-1.))
qdyn = p0 * ((1.0 + rho0 * Vcas * Vcas / (7.0 * p0)) ** 3.5 - 1.0)
Vtas = np.sqrt(7.0 * p / rho * ((1.0 + qdyn / p) ** (2.0 / 7.0) - 1.0))
return Vtas
def tas2cas(Vtas, H):
"""True Airspeed to Calibrated Airspeed"""
p, rho, T = atmos(H)
qdyn = p*((1.+rho*Vtas*Vtas/(7.*p))**3.5-1.)
Vcas = np.sqrt(7.*p0/rho0*((qdyn/p0+1.)**(2./7.)-1.))
qdyn = p * ((1.0 + rho * Vtas * Vtas / (7.0 * p)) ** 3.5 - 1.0)
Vcas = np.sqrt(7.0 * p0 / rho0 * ((qdyn / p0 + 1.0) ** (2.0 / 7.0) - 1.0))
return Vcas

162
pyModeS/extra/rtlreader.py Normal file
View File

@@ -0,0 +1,162 @@
import numpy as np
import pyModeS as pms
from rtlsdr import RtlSdr
import time
modes_sample_rate = 2e6
modes_frequency = 1090e6
buffer_size = 1024 * 100
read_size = 1024 * 20
pbits = 8
fbits = 112
preamble = [1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0]
th_amp = 0.2 # signal amplitude threshold for 0 and 1 bit
th_amp_diff = 0.8 # signal amplitude threshold difference between 0 and 1 bit
class RtlReader(object):
def __init__(self, **kwargs):
super(RtlReader, self).__init__()
self.signal_buffer = []
self.sdr = RtlSdr()
self.sdr.sample_rate = modes_sample_rate
self.sdr.center_freq = modes_frequency
self.sdr.gain = "auto"
# sdr.freq_correction = 75
self.debug = kwargs.get("debug", False)
self.raw_pipe_in = None
self.stop_flag = False
def _process_buffer(self):
messages = []
# signal_array = np.array(self.signal_buffer)
# pulses_array = np.where(np.array(self.signal_buffer) < th_amp, 0, 1)
# pulses = "".join(str(x) for x in pulses_array)
buffer_length = len(self.signal_buffer)
i = 0
while i < buffer_length:
if self.signal_buffer[i] < th_amp:
i += 1
continue
# if pulses[i : i + pbits * 2] == preamble:
if self._check_preamble(self.signal_buffer[i : i + pbits * 2]):
frame_start = i + pbits * 2
frame_end = i + pbits * 2 + (fbits + 1) * 2
frame_length = (fbits + 1) * 2
frame_pulses = self.signal_buffer[frame_start:frame_end]
msgbin = ""
for j in range(0, frame_length, 2):
p2 = frame_pulses[j : j + 2]
if len(p2) < 2:
break
if p2[0] < th_amp and p2[1] < th_amp:
break
elif p2[0] >= p2[1]:
c = "1"
elif p2[0] < p2[1]:
c = "0"
else:
msgbin = ""
break
msgbin += c
# advance i with a jump
i = frame_start + j
if len(msgbin) > 0:
msghex = pms.bin2hex(msgbin)
if self._check_msg(msghex):
messages.append([msghex, time.time()])
if self.debug:
self._debug_msg(msghex)
elif i > buffer_length - 500:
# save some for next process
break
else:
i += 1
# keep reminder of buffer for next iteration
self.signal_buffer = self.signal_buffer[i:]
return messages
def _check_preamble(self, pulses):
if len(pulses) != 16:
return False
for i in range(16):
if abs(pulses[i] - preamble[i]) > th_amp_diff:
return False
return True
def _check_msg(self, msg):
df = pms.df(msg)
msglen = len(msg)
if df == 17 and msglen == 28:
if pms.crc(msg) == 0:
return True
elif df in [20, 21] and msglen == 28:
return True
elif df in [4, 5, 11] and msglen == 14:
return True
def _debug_msg(self, msg):
df = pms.df(msg)
msglen = len(msg)
if df == 17 and msglen == 28:
print(msg, pms.icao(msg), pms.crc(msg))
elif df in [20, 21] and msglen == 28:
print(msg, pms.icao(msg))
elif df in [4, 5, 11] and msglen == 14:
print(msg, pms.icao(msg))
else:
# print("[*]", msg)
pass
def _read_callback(self, data, rtlsdr_obj):
# scaling signal (imporatant)
amp = np.absolute(data)
amp_norm = np.interp(amp, (amp.min(), amp.max()), (0, 1))
self.signal_buffer.extend(amp_norm.tolist())
if len(self.signal_buffer) >= buffer_size:
messages = self._process_buffer()
self.handle_messages(messages)
def handle_messages(self, messages):
"""re-implement this method to handle the messages"""
for msg, t in messages:
# print("%15.9f %s" % (t, msg))
pass
def stop(self, *args, **kwargs):
self.sdr.cancel_read_async()
def run(self, raw_pipe_in=None, stop_flag=None):
self.raw_pipe_in = raw_pipe_in
self.stop_flag = stop_flag
self.sdr.read_samples_async(self._read_callback, read_size)
# count = 1
# while count < 1000:
# count += 1
# data = self.sdr.read_samples(read_size)
# self._read_callback(data, None)
if __name__ == "__main__":
import signal
rtl = RtlReader()
signal.signal(signal.SIGINT, rtl.stop)
rtl.debug = True
rtl.run()

View File

@@ -1,55 +1,55 @@
'''
Stream beast raw data from a TCP server, convert to mode-s messages
'''
"""Stream beast raw data from a TCP server, convert to mode-s messages."""
from __future__ import print_function, division
import os
import sys
import socket
import time
import pyModeS as pms
from threading import Thread
import traceback
import zmq
if (sys.version_info > (3, 0)):
if sys.version_info > (3, 0):
PY_VERSION = 3
else:
PY_VERSION = 2
class BaseClient(Thread):
def __init__(self, host, port, rawtype):
Thread.__init__(self)
class TcpClient(object):
def __init__(self, host, port, datatype):
super(TcpClient, self).__init__()
self.host = host
self.port = port
self.buffer = []
self.rawtype = rawtype
if self.rawtype not in ['avr', 'beast', 'skysense']:
print("rawtype must be either avr, beast or skysense")
self.socket = None
self.datatype = datatype
if self.datatype not in ["raw", "beast", "skysense"]:
print("datatype must be either raw, beast or skysense")
os._exit(1)
self.raw_pipe_in = None
self.stop_flag = False
def connect(self):
while True:
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(10) # 10 second timeout
s.connect((self.host, self.port))
print("Server connected - %s:%s" % (self.host, self.port))
print("collecting ADS-B messages...")
return s
except socket.error as err:
print("Socket connection error: %s. reconnecting..." % err)
time.sleep(3)
self.socket = zmq.Context().socket(zmq.STREAM)
self.socket.setsockopt(zmq.LINGER, 0)
self.socket.setsockopt(zmq.RCVTIMEO, 10000)
self.socket.connect("tcp://%s:%s" % (self.host, self.port))
def stop(self):
self.socket.disconnect()
def read_avr_buffer(self):
# -- testing --
# for b in self.buffer:
# print(chr(b), b)
# Append message with 0-9,A-F,a-f, until stop sign
def read_raw_buffer(self):
""" Read raw ADS-B data type.
String strats with "*" and ends with ";". For example:
*5d484ba898f8c6;
*8d400cd5990d7e9a10043e5e6da0;
*a0001498be800030aa0000c7a75f;
"""
messages = []
msg_stop = False
self.current_msg = ""
for b in self.buffer:
if b == 59:
msg_stop = True
@@ -57,9 +57,9 @@ class BaseClient(Thread):
messages.append([self.current_msg, ts])
if b == 42:
msg_stop = False
self.current_msg = ''
self.current_msg = ""
if (not msg_stop) and (48<=b<=57 or 65<=b<=70 or 97<=b<=102):
if (not msg_stop) and (48 <= b <= 57 or 65 <= b <= 70 or 97 <= b <= 102):
self.current_msg = self.current_msg + chr(b)
self.buffer = []
@@ -67,7 +67,8 @@ class BaseClient(Thread):
return messages
def read_beast_buffer(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,
@@ -81,8 +82,7 @@ class BaseClient(Thread):
timestamp:
wiki.modesbeast.com/Radarcape:Firmware_Versions#The_GPS_timestamp
'''
"""
messages_mlat = []
msg = []
i = 0
@@ -91,16 +91,16 @@ class BaseClient(Thread):
# then, reset the self.buffer with the remainder
while i < len(self.buffer):
if (self.buffer[i:i+2] == [0x1a, 0x1a]):
msg.append(0x1a)
if self.buffer[i : i + 2] == [0x1A, 0x1A]:
msg.append(0x1A)
i += 1
elif (i == len(self.buffer) - 1) and (self.buffer[i] == 0x1a):
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:
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)
msg.append(0x1A)
elif len(msg) > 0:
messages_mlat.append(msg)
msg = []
@@ -112,12 +112,12 @@ class BaseClient(Thread):
if len(msg) > 0:
reminder = []
for i, m in enumerate(msg):
if (m == 0x1a) and (i < len(msg)-1):
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
self.buffer = [0x1A] + msg
else:
self.buffer = []
@@ -131,14 +131,17 @@ class BaseClient(Thread):
if msgtype == 0x32:
# Mode-S Short Message, 7 byte, 14-len hexstr
msg = ''.join('%02X' % i for i in mm[8:15])
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])
msg = "".join("%02X" % i for i in mm[8:22])
else:
# Other message tupe
continue
if len(msg) not in [14, 28]:
continue
df = pms.df(msg)
# skip incomplete message
@@ -215,25 +218,33 @@ class BaseClient(Thread):
messages = []
while len(self.buffer) > SS_MSGLENGTH:
i = 0
if self.buffer[i] == SS_STARTCHAR and self.buffer[i+SS_MSGLENGTH] == SS_STARTCHAR:
if (
self.buffer[i] == SS_STARTCHAR
and self.buffer[i + SS_MSGLENGTH] == SS_STARTCHAR
):
i += 1
if (self.buffer[i]>>7):
#Long message
payload = self.buffer[i:i+14]
if self.buffer[i] >> 7:
# Long message
payload = self.buffer[i : i + 14]
else:
#Short message
payload = self.buffer[i:i+7]
msg = ''.join('%02X' % j for j in payload)
i += 14 #Both message types use 14 bytes
tsbin = self.buffer[i:i+6]
sec = ( (tsbin[0] & 0x7f) << 10) | (tsbin[1] << 2 ) | (tsbin[2] >> 6)
nano = ( (tsbin[2] & 0x3f) << 24) | (tsbin[3] << 16) | (tsbin[4] << 8) | tsbin[5]
ts = sec + nano*1.0e-9
# Short message
payload = self.buffer[i : i + 7]
msg = "".join("%02X" % j for j in payload)
i += 14 # Both message types use 14 bytes
tsbin = self.buffer[i : i + 6]
sec = ((tsbin[0] & 0x7F) << 10) | (tsbin[1] << 2) | (tsbin[2] >> 6)
nano = (
((tsbin[2] & 0x3F) << 24)
| (tsbin[3] << 16)
| (tsbin[4] << 8)
| tsbin[5]
)
ts = sec + nano * 1.0e-9
i += 6
#Signal and noise level - Don't care for now
# Signal and noise level - Don't care for now
i += 3
self.buffer = self.buffer[SS_MSGLENGTH:]
messages.append( [msg,ts] )
messages.append([msg, ts])
else:
self.buffer = self.buffer[1:]
return messages
@@ -243,12 +254,14 @@ class BaseClient(Thread):
for msg, t in messages:
print("%15.9f %s" % (t, msg))
def run(self):
sock = self.connect()
def run(self, raw_pipe_in=None, stop_flag=None):
self.raw_pipe_in = raw_pipe_in
self.stop_flag = stop_flag
self.connect()
while True:
try:
received = sock.recv(1024)
received = [i for i in self.socket.recv(4096)]
if PY_VERSION == 2:
received = [ord(i) for i in received]
@@ -261,11 +274,11 @@ class BaseClient(Thread):
# continue
# -- Removed!! Cause delay in low data rate scenario --
if self.rawtype == 'beast':
if self.datatype == "beast":
messages = self.read_beast_buffer()
elif self.rawtype == 'avr':
messages = self.read_avr_buffer()
elif self.rawtype == 'skysense':
elif self.datatype == "raw":
messages = self.read_raw_buffer()
elif self.datatype == "skysense":
messages = self.read_skysense_buffer()
if not messages:
@@ -273,14 +286,12 @@ class BaseClient(Thread):
else:
self.handle_messages(messages)
time.sleep(0.001)
except Exception as e:
# Provides the user an option to supply the environment
# variable PYMODES_DEBUG to halt the execution
# for debugging purposes
debug_intent = os.environ.get('PYMODES_DEBUG', 'false')
if debug_intent.lower() == 'true':
debug_intent = os.environ.get("PYMODES_DEBUG", "false")
if debug_intent.lower() == "true":
traceback.print_exc()
sys.exit()
else:
@@ -288,15 +299,15 @@ class BaseClient(Thread):
try:
sock = self.connect()
time.sleep(1)
except Exception as e:
print("Unexpected Error:", e)
if __name__ == '__main__':
if __name__ == "__main__":
# for testing purpose only
host = sys.argv[1]
port = int(sys.argv[2])
rawtype = sys.argv[3]
client = BaseClient(host=host, port=port, rawtype=rawtype)
client.daemon = True
datatype = sys.argv[3]
client = TcpClient(host=host, port=port, datatype=datatype)
client.run()

284
pyModeS/streamer/decode.py Normal file
View File

@@ -0,0 +1,284 @@
from __future__ import absolute_import, print_function, division
import os
import time
import datetime
import csv
import pyModeS as pms
class Decode:
def __init__(self, latlon=None, dumpto=None):
self.acs = dict()
if latlon is not None:
self.lat0 = float(latlon[0])
self.lon0 = float(latlon[1])
else:
self.lat0 = None
self.lon0 = None
self.t = 0
self.cache_timeout = 60 # seconds
if dumpto is not None and os.path.isdir(dumpto):
self.dumpto = dumpto
else:
self.dumpto = None
def process_raw(self, adsb_ts, adsb_msg, commb_ts, commb_msg, tnow=None):
"""process a chunk of adsb and commb messages recieved in the same
time period.
"""
if tnow is None:
tnow = time.time()
self.t = tnow
local_updated_acs_buffer = []
output_buffer = []
# process adsb message
for t, msg in zip(adsb_ts, adsb_msg):
icao = pms.icao(msg)
tc = pms.adsb.typecode(msg)
if icao not in self.acs:
self.acs[icao] = {
"live": None,
"call": None,
"lat": None,
"lon": None,
"alt": None,
"gs": None,
"trk": None,
"roc": None,
"tas": None,
"roll": None,
"rtrk": None,
"ias": None,
"mach": None,
"hdg": None,
"ver": None,
"HPL": None,
"RCu": None,
"RCv": None,
"HVE": None,
"VVE": None,
"Rc": None,
"VPL": None,
"EPU": None,
"VEPU": None,
"HFOMr": None,
"VFOMr": None,
"PE_RCu": None,
"PE_VPL": None,
}
self.acs[icao]["t"] = t
self.acs[icao]["live"] = int(t)
if 1 <= tc <= 4:
cs = pms.adsb.callsign(msg)
self.acs[icao]["call"] = cs
output_buffer.append([t, icao, "cs", cs])
if (5 <= tc <= 8) or (tc == 19):
vdata = pms.adsb.velocity(msg)
if vdata is None:
continue
spd, trk, roc, tag = vdata
if tag != "GS":
continue
if (spd is None) or (trk is None):
continue
self.acs[icao]["gs"] = spd
self.acs[icao]["trk"] = trk
self.acs[icao]["roc"] = roc
self.acs[icao]["tv"] = t
output_buffer.append([t, icao, "gs", spd])
output_buffer.append([t, icao, "trk", trk])
output_buffer.append([t, icao, "roc", roc])
if 5 <= tc <= 18:
oe = pms.adsb.oe_flag(msg)
self.acs[icao][oe] = msg
self.acs[icao]["t" + str(oe)] = t
if ("tpos" in self.acs[icao]) and (t - self.acs[icao]["tpos"] < 180):
# use single message decoding
rlat = self.acs[icao]["lat"]
rlon = self.acs[icao]["lon"]
latlon = pms.adsb.position_with_ref(msg, rlat, rlon)
elif (
("t0" in self.acs[icao])
and ("t1" in self.acs[icao])
and (abs(self.acs[icao]["t0"] - self.acs[icao]["t1"]) < 10)
):
# use multi message decoding
try:
latlon = pms.adsb.position(
self.acs[icao][0],
self.acs[icao][1],
self.acs[icao]["t0"],
self.acs[icao]["t1"],
self.lat0,
self.lon0,
)
except:
# mix of surface and airborne position message
continue
else:
latlon = None
if latlon is not None:
self.acs[icao]["tpos"] = t
self.acs[icao]["lat"] = latlon[0]
self.acs[icao]["lon"] = latlon[1]
alt = pms.adsb.altitude(msg)
self.acs[icao]["alt"] = alt
output_buffer.append([t, icao, "lat", latlon[0]])
output_buffer.append([t, icao, "lon", latlon[1]])
output_buffer.append([t, icao, "alt", alt])
local_updated_acs_buffer.append(icao)
# Uncertainty & accuracy
ac = self.acs[icao]
if 9 <= tc <= 18:
ac["nic_bc"] = pms.adsb.nic_b(msg)
if (5 <= tc <= 8) or (9 <= tc <= 18) or (20 <= tc <= 22):
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"])
elif (
(ac["ver"] == 2)
and ("nic_a" in ac.keys())
and ("nic_bc" in ac.keys())
):
ac["Rc"] = pms.adsb.nic_v2(msg, ac["nic_a"], ac["nic_bc"])
if tc == 19:
ac["HVE"], ac["VVE"] = pms.adsb.nuc_v(msg)
if ac["ver"] in [1, 2]:
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["EPU"], ac["VEPU"] = pms.adsb.nac_p(msg)
if tc == 31:
ac["ver"] = pms.adsb.version(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"]
)
if ac["ver"] == 1:
ac["nic_s"] = pms.adsb.nic_s(msg)
elif ac["ver"] == 2:
ac["nic_a"], ac["nic_bc"] = pms.adsb.nic_a_c(msg)
# process commb message
for t, msg in zip(commb_ts, commb_msg):
icao = pms.icao(msg)
if icao not in self.acs:
continue
self.acs[icao]["live"] = int(t)
bds = pms.bds.infer(msg)
if bds == "BDS50":
roll50 = pms.commb.roll50(msg)
trk50 = pms.commb.trk50(msg)
rtrk50 = pms.commb.rtrk50(msg)
gs50 = pms.commb.gs50(msg)
tas50 = pms.commb.tas50(msg)
self.acs[icao]["t50"] = t
if tas50:
self.acs[icao]["tas"] = tas50
output_buffer.append([t, icao, "tas50", tas50])
if roll50:
self.acs[icao]["roll"] = roll50
output_buffer.append([t, icao, "roll50", roll50])
if rtrk50:
self.acs[icao]["rtrk"] = rtrk50
output_buffer.append([t, icao, "rtrk50", rtrk50])
if trk50:
output_buffer.append([t, icao, "trk50", trk50])
if gs50:
output_buffer.append([t, icao, "gs50", gs50])
elif bds == "BDS60":
ias60 = pms.commb.ias60(msg)
hdg60 = pms.commb.hdg60(msg)
mach60 = pms.commb.mach60(msg)
roc60baro = pms.commb.vr60baro(msg)
roc60ins = pms.commb.vr60ins(msg)
if ias60 or hdg60 or mach60:
self.acs[icao]["t60"] = t
if ias60:
self.acs[icao]["ias"] = ias60
if hdg60:
self.acs[icao]["hdg"] = hdg60
if mach60:
self.acs[icao]["mach"] = mach60
if roc60baro:
output_buffer.append([t, icao, "roc60baro", roc60baro])
if roc60ins:
output_buffer.append([t, icao, "roc60ins", roc60ins])
# clear up old data
for icao in list(self.acs.keys()):
if self.t - self.acs[icao]["live"] > self.cache_timeout:
del self.acs[icao]
continue
if self.dumpto is not None:
dh = str(datetime.datetime.now().strftime("%Y%m%d_%H"))
fn = self.dumpto + "/pymodes_dump_%s.csv" % dh
output_buffer.sort(key=lambda x: x[0])
with open(fn, "a") as f:
writer = csv.writer(f)
writer.writerows(output_buffer)
return
def get_aircraft(self):
"""all aircraft that are stored in memeory"""
acs = self.acs
return acs
def run(self, raw_pipe_out, ac_pipe_in):
local_buffer = []
while True:
while raw_pipe_out.poll():
data = raw_pipe_out.recv()
local_buffer.append(data)
for data in local_buffer:
self.process_raw(
data["adsb_ts"],
data["adsb_msg"],
data["commb_ts"],
data["commb_msg"],
)
local_buffer = []
acs = self.get_aircraft()
ac_pipe_in.send(acs)
time.sleep(0.001)

View File

@@ -3,118 +3,132 @@
from __future__ import print_function, division
import os
import sys
import time
import argparse
import curses
from threading import Lock
import pyModeS as pms
from pyModeS.extra.tcpclient import BaseClient
from pyModeS.streamer.stream import Stream
import signal
import multiprocessing
from pyModeS.streamer.decode import Decode
from pyModeS.streamer.screen import Screen
LOCK = Lock()
ADSB_MSG = []
ADSB_TS = []
COMMB_MSG = []
COMMB_TS = []
parser = argparse.ArgumentParser()
parser.add_argument('--server', help='server address or IP', required=True)
parser.add_argument('--port', help='raw data port', required=True)
parser.add_argument('--rawtype', help='beast, avr or skysense', required=True)
parser.add_argument('--latlon', help='receiver position', nargs=2, metavar=('LAT', 'LON'), required=True)
parser.add_argument('--show-uncertainty', dest='uncertainty', help='display uncertaint values, default off', action='store_true', required=False, default=False)
parser.add_argument('--dumpto', help='folder to dump decoded output', required=False, default=None)
args = parser.parse_args()
SERVER = args.server
PORT = int(args.port)
RAWTYPE = args.rawtype
LAT0 = float(args.latlon[0])
LON0 = float(args.latlon[1])
UNCERTAINTY = args.uncertainty
DUMPTO = args.dumpto
if DUMPTO is not None:
# append to current folder except root is given
if DUMPTO[0] != '/':
DUMPTO = os.getcwd() + '/' + DUMPTO
if not os.path.isdir(DUMPTO):
print('Error: dump folder (%s) does not exist' % DUMPTO)
sys.exit(1)
class ModesClient(BaseClient):
def __init__(self, host, port, rawtype):
super(ModesClient, self).__init__(host, port, rawtype)
def handle_messages(self, messages):
local_buffer_adsb_msg = []
local_buffer_adsb_ts = []
local_buffer_ehs_msg = []
local_buffer_ehs_ts = []
for msg, t in messages:
if len(msg) < 28: # only process long messages
continue
df = pms.df(msg)
if df == 17 or df == 18:
local_buffer_adsb_msg.append(msg)
local_buffer_adsb_ts.append(t)
elif df == 20 or df == 21:
local_buffer_ehs_msg.append(msg)
local_buffer_ehs_ts.append(t)
else:
continue
LOCK.acquire()
ADSB_MSG.extend(local_buffer_adsb_msg)
ADSB_TS.extend(local_buffer_adsb_ts)
COMMB_MSG.extend(local_buffer_ehs_msg)
COMMB_TS.extend(local_buffer_ehs_ts)
LOCK.release()
from pyModeS.streamer.source import NetSource, RtlSdrSource
# redirect all stdout to null, avoiding messing up with the screen
sys.stdout = open(os.devnull, 'w')
sys.stdout = open(os.devnull, "w")
client = ModesClient(host=SERVER, port=PORT, rawtype=RAWTYPE)
client.daemon = True
client.start()
stream = Stream(lat0=LAT0, lon0=LON0, dumpto=DUMPTO)
support_rawtypes = ["raw", "beast", "skysense"]
try:
screen = Screen(uncertainty=UNCERTAINTY)
screen.daemon = True
screen.start()
parser = argparse.ArgumentParser()
parser.add_argument(
"--source",
help='Choose data source, "rtlsdr" or "net"',
required=True,
default="net",
)
parser.add_argument(
"--connect",
help="Define server, port and data type. Supported data types are: %s"
% support_rawtypes,
nargs=3,
metavar=("SERVER", "PORT", "DATATYPE"),
default=None,
required=False,
)
parser.add_argument(
"--latlon",
help="Receiver latitude and longitude, needed for the surface position, default none",
nargs=2,
metavar=("LAT", "LON"),
default=None,
required=False,
)
parser.add_argument(
"--show-uncertainty",
dest="uncertainty",
help="Display uncertainty values, default off",
action="store_true",
required=False,
default=False,
)
parser.add_argument(
"--dumpto",
help="Folder to dump decoded output, default none",
required=False,
default=None,
)
args = parser.parse_args()
while True:
if len(ADSB_MSG) > 200:
LOCK.acquire()
stream.process_raw(ADSB_TS, ADSB_MSG, COMMB_TS, COMMB_MSG)
ADSB_MSG = []
ADSB_TS = []
COMMB_MSG = []
COMMB_TS = []
LOCK.release()
SOURCE = args.source
LATLON = args.latlon
UNCERTAINTY = args.uncertainty
DUMPTO = args.dumpto
acs = stream.get_aircraft()
try:
screen.update_data(acs)
screen.update()
time.sleep(0.02)
except KeyboardInterrupt:
raise
except:
continue
if SOURCE == "rtlsdr":
pass
elif SOURCE == "net":
if args.connect is None:
print("Error: --connect argument must not be empty.")
else:
SERVER, PORT, DATATYPE = args.connect
if DATATYPE not in support_rawtypes:
print("Data type not supported, avaiable ones are %s" % support_rawtypes)
except KeyboardInterrupt:
sys.exit(0)
else:
print('Source must be "rtlsdr" or "net".')
sys.exit(1)
finally:
if DUMPTO is not None:
# append to current folder except root is given
if DUMPTO[0] != "/":
DUMPTO = os.getcwd() + "/" + DUMPTO
if not os.path.isdir(DUMPTO):
print("Error: dump folder (%s) does not exist" % DUMPTO)
sys.exit(1)
# raw_event = multiprocessing.Event()
# ac_event = multiprocessing.Event()
# raw_queue = multiprocessing.Queue()
# ac_queue = multiprocessing.Queue()
raw_pipe_in, raw_pipe_out = multiprocessing.Pipe()
ac_pipe_in, ac_pipe_out = multiprocessing.Pipe()
stop_flag = multiprocessing.Value("b", False)
if SOURCE == "net":
source = NetSource(host=SERVER, port=PORT, rawtype=DATATYPE)
elif SOURCE == "rtlsdr":
source = RtlSdrSource()
recv_process = multiprocessing.Process(target=source.run, args=(raw_pipe_in, stop_flag))
decode = Decode(latlon=LATLON, dumpto=DUMPTO)
decode_process = multiprocessing.Process(
target=decode.run, args=(raw_pipe_out, ac_pipe_in)
)
screen = Screen(uncertainty=UNCERTAINTY)
screen_process = multiprocessing.Process(target=screen.run, args=(ac_pipe_out,))
def closeall(signal, frame):
print("KeyboardInterrupt (ID: {}). Cleaning up...".format(signal))
stop_flag.value = True
curses.endwin()
recv_process.terminate()
decode_process.terminate()
screen_process.terminate()
recv_process.join()
decode_process.join()
screen_process.join()
exit(0)
signal.signal(signal.SIGINT, closeall)
recv_process.start()
decode_process.start()
screen_process.start()

View File

@@ -1,46 +1,46 @@
from __future__ import print_function, division
import os
import curses
import numpy as np
import time
from threading import Thread
import threading
COLUMNS = [
('call', 10),
('lat', 10),
('lon', 10),
('alt', 7),
('gs', 5),
('tas', 5),
('ias', 5),
('mach', 7),
('roc', 7),
('trk', 10),
('hdg', 10),
('live', 6),
("call", 10),
("lat", 10),
("lon", 10),
("alt", 7),
("gs", 5),
("tas", 5),
("ias", 5),
("mach", 7),
("roc", 7),
("trk", 10),
("hdg", 10),
("live", 6),
]
UNCERTAINTY_COLUMNS = [
('|', 5),
('ver', 4),
('HPL', 5),
('RCu', 5),
('RCv', 5),
('HVE', 5),
('VVE', 5),
('Rc', 4),
('VPL', 5),
('EPU', 5),
('VEPU', 6),
('HFOMr', 7),
('VFOMr', 7),
('PE_RCu', 8),
('PE_VPL', 8),
("|", 5),
("ver", 4),
("HPL", 5),
("RCu", 5),
("RCv", 5),
("HVE", 5),
("VVE", 5),
("Rc", 4),
("VPL", 5),
("EPU", 5),
("VEPU", 6),
("HFOMr", 7),
("VFOMr", 7),
("PE_RCu", 8),
("PE_VPL", 8),
]
class Screen(Thread):
class Screen(object):
def __init__(self, uncertainty=False):
Thread.__init__(self)
super(Screen, self).__init__()
self.screen = curses.initscr()
curses.noecho()
curses.mousemask(1)
@@ -55,16 +55,20 @@ class Screen(Thread):
if uncertainty:
self.columns.extend(UNCERTAINTY_COLUMNS)
def reset_cursor_pos(self):
self.screen.move(self.y, self.x)
def update_data(self, acs):
def update_ac(self, acs):
self.acs = acs
def draw_frame(self):
self.screen.border(0)
self.screen.addstr(0, 2, "Online aircraft [%d] ('Ctrl+C' to exit, 'Enter' to lock one)" % len(self.acs))
self.screen.addstr(
0,
2,
"Online aircraft [%d] ('Ctrl+C' to exit, 'Enter' to lock one)"
% len(self.acs),
)
def update(self):
if len(self.acs) == 0:
@@ -81,21 +85,20 @@ class Screen(Thread):
row = 1
header = ' icao'
header = " icao"
for c, cw in self.columns:
header += (cw-len(c))*' ' + c
header += (cw - len(c)) * " " + c
# fill end with spaces
header += (self.scr_w - 2 - len(header)) * ' '
header += (self.scr_w - 2 - len(header)) * " "
if len(header) > self.scr_w - 2:
header = header[:self.scr_w-3] + '>'
header = header[: self.scr_w - 3] + ">"
self.screen.addstr(row, 1, header)
row +=1
self.screen.addstr(row, 1, '-'*(self.scr_w-2))
row += 1
self.screen.addstr(row, 1, "-" * (self.scr_w - 2))
icaos = np.array(list(self.acs.keys()))
icaos = np.sort(icaos)
@@ -105,10 +108,10 @@ class Screen(Thread):
idx = row + self.offset - 3
if idx > len(icaos) - 1:
line = ' '*(self.scr_w-2)
line = " " * (self.scr_w - 2)
else:
line = ''
line = ""
icao = icaos[idx]
ac = self.acs[icao]
@@ -116,22 +119,22 @@ class Screen(Thread):
line += icao
for c, cw in self.columns:
if c=='|':
val = '|'
elif c=='live':
val = str(int(time.time() - ac[c]))+'s'
if c == "|":
val = "|"
elif c == "live":
val = str(ac[c] - int(time.time())) + "s"
elif ac[c] is None:
val = ''
val = ""
else:
val = ac[c]
val_str = str(val)
line += (cw-len(val_str))*' ' + val_str
line += (cw - len(val_str)) * " " + val_str
# fill end with spaces
line += (self.scr_w - 2 - len(line)) * ' '
line += (self.scr_w - 2 - len(line)) * " "
if len(line) > self.scr_w - 2:
line = line[:self.scr_w-3] + '>'
line = line[: self.scr_w - 3] + ">"
if (icao is not None) and (self.lock_icao == icao):
self.screen.addstr(row, 1, line, curses.A_STANDOUT)
@@ -140,15 +143,15 @@ class Screen(Thread):
else:
self.screen.addstr(row, 1, line)
self.screen.addstr(self.scr_h-3, 1, '-'*(self.scr_w-2))
self.screen.addstr(self.scr_h - 3, 1, "-" * (self.scr_w - 2))
total_page = len(icaos) // (self.scr_h - 4) + 1
current_page = self.offset // (self.scr_h - 4) + 1
self.screen.addstr(self.scr_h-2, 1, '(%d / %d)' % (current_page, total_page))
self.screen.addstr(self.scr_h - 2, 1, "(%d / %d)" % (current_page, total_page))
self.reset_cursor_pos()
def run(self):
def kye_handling(self):
self.draw_frame()
self.scr_h, self.scr_w = self.screen.getmaxyx()
@@ -168,7 +171,7 @@ class Screen(Thread):
self.offset = offset_intent
else:
self.offset = 0
elif c == curses.KEY_DOWN :
elif c == curses.KEY_DOWN:
y_intent = self.y + 1
if y_intent < self.scr_h - 3:
self.y = y_intent
@@ -178,6 +181,30 @@ class Screen(Thread):
self.y = y_intent
elif c == curses.KEY_ENTER or c == 10 or c == 13:
self.lock_icao = (self.screen.instr(self.y, 1, 6)).decode()
elif c == 27: # escape key
self.lock_icao = None
elif c == curses.KEY_F5:
self.screen.refresh()
self.draw_frame()
def run(self, ac_pipe_out):
local_buffer = []
key_thread = threading.Thread(target=self.kye_handling)
key_thread.start()
while True:
while ac_pipe_out.poll():
acs = ac_pipe_out.recv()
local_buffer.append(acs)
for acs in local_buffer:
self.update_ac(acs)
local_buffer = []
try:
self.update()
except:
pass
time.sleep(0.001)

View File

@@ -0,0 +1,91 @@
import pyModeS as pms
from pyModeS.extra.tcpclient import TcpClient
from pyModeS.extra.rtlreader import RtlReader
class NetSource(TcpClient):
def __init__(self, host, port, rawtype):
super(NetSource, self).__init__(host, port, rawtype)
self.reset_local_buffer()
def reset_local_buffer(self):
self.local_buffer_adsb_msg = []
self.local_buffer_adsb_ts = []
self.local_buffer_commb_msg = []
self.local_buffer_commb_ts = []
def handle_messages(self, messages):
if self.stop_flag.value is True:
self.stop()
return
for msg, t in messages:
if len(msg) < 28: # only process long messages
continue
df = pms.df(msg)
if df == 17 or df == 18:
self.local_buffer_adsb_msg.append(msg)
self.local_buffer_adsb_ts.append(t)
elif df == 20 or df == 21:
self.local_buffer_commb_msg.append(msg)
self.local_buffer_commb_ts.append(t)
else:
continue
if len(self.local_buffer_adsb_msg) > 1:
self.raw_pipe_in.send(
{
"adsb_ts": self.local_buffer_adsb_ts,
"adsb_msg": self.local_buffer_adsb_msg,
"commb_ts": self.local_buffer_commb_ts,
"commb_msg": self.local_buffer_commb_msg,
}
)
self.reset_local_buffer()
class RtlSdrSource(RtlReader):
def __init__(self):
super(RtlSdrSource, self).__init__()
self.reset_local_buffer()
def reset_local_buffer(self):
self.local_buffer_adsb_msg = []
self.local_buffer_adsb_ts = []
self.local_buffer_commb_msg = []
self.local_buffer_commb_ts = []
def handle_messages(self, messages):
if self.stop_flag.value is True:
self.stop()
return
for msg, t in messages:
if len(msg) < 28: # only process long messages
continue
df = pms.df(msg)
if df == 17 or df == 18:
self.local_buffer_adsb_msg.append(msg)
self.local_buffer_adsb_ts.append(t)
elif df == 20 or df == 21:
self.local_buffer_commb_msg.append(msg)
self.local_buffer_commb_ts.append(t)
else:
continue
if len(self.local_buffer_adsb_msg) > 1:
self.raw_pipe_in.send(
{
"adsb_ts": self.local_buffer_adsb_ts,
"adsb_msg": self.local_buffer_adsb_msg,
"commb_ts": self.local_buffer_commb_ts,
"commb_msg": self.local_buffer_commb_msg,
}
)
self.reset_local_buffer()

View File

@@ -1,255 +0,0 @@
from __future__ import absolute_import, print_function, division
import os
import time
import datetime
import csv
import pyModeS as pms
class Stream():
def __init__(self, lat0, lon0, dumpto=None):
self.acs = dict()
self.lat0 = lat0
self.lon0 = lon0
self.t = 0
self.cache_timeout = 60 # seconds
if dumpto is not None and os.path.isdir(dumpto):
self.dumpto = dumpto
else:
self.dumpto = None
def process_raw(self, adsb_ts, adsb_msgs, commb_ts, commb_msgs, tnow=None):
"""process a chunk of adsb and commb messages recieved in the same
time period.
"""
if tnow is None:
tnow = time.time()
self.t = tnow
local_updated_acs_buffer = []
output_buffer = []
# process adsb message
for t, msg in zip(adsb_ts, adsb_msgs):
icao = pms.icao(msg)
tc = pms.adsb.typecode(msg)
if icao not in self.acs:
self.acs[icao] = {
'live': None,
'call': None,
'lat': None,
'lon': None,
'alt': None,
'gs': None,
'trk': None,
'roc': None,
'tas': None,
'roll': None,
'rtrk': None,
'ias': None,
'mach': None,
'hdg': None,
'ver' : None,
'HPL' : None,
'RCu' : None,
'RCv' : None,
'HVE' : None,
'VVE' : None,
'Rc' : None,
'VPL' : None,
'EPU' : None,
'VEPU' : None,
'HFOMr' : None,
'VFOMr' : None,
'PE_RCu' : None,
'PE_VPL' : None,
}
self.acs[icao]['t'] = t
self.acs[icao]['live'] = int(t)
if 1 <= tc <= 4:
cs = pms.adsb.callsign(msg)
self.acs[icao]['call'] = cs
output_buffer.append([t, icao, 'cs', cs])
if (5 <= tc <= 8) or (tc == 19):
vdata = pms.adsb.velocity(msg)
if vdata is None:
continue
spd, trk, roc, tag = vdata
if tag != 'GS':
continue
if (spd is None) or (trk is None):
continue
self.acs[icao]['gs'] = spd
self.acs[icao]['trk'] = trk
self.acs[icao]['roc'] = roc
self.acs[icao]['tv'] = t
output_buffer.append([t, icao, 'gs', spd])
output_buffer.append([t, icao, 'trk', trk])
output_buffer.append([t, icao, 'roc', roc])
if (5 <= tc <= 18):
oe = pms.adsb.oe_flag(msg)
self.acs[icao][oe] = msg
self.acs[icao]['t'+str(oe)] = t
if ('tpos' in self.acs[icao]) and (t - self.acs[icao]['tpos'] < 180):
# use single message decoding
rlat = self.acs[icao]['lat']
rlon = self.acs[icao]['lon']
latlon = pms.adsb.position_with_ref(msg, rlat, rlon)
elif ('t0' in self.acs[icao]) and ('t1' in self.acs[icao]) and \
(abs(self.acs[icao]['t0'] - self.acs[icao]['t1']) < 10):
# use multi message decoding
try:
latlon = pms.adsb.position(
self.acs[icao][0],
self.acs[icao][1],
self.acs[icao]['t0'],
self.acs[icao]['t1'],
self.lat0, self.lon0
)
except:
# mix of surface and airborne position message
continue
else:
latlon = None
if latlon is not None:
self.acs[icao]['tpos'] = t
self.acs[icao]['lat'] = latlon[0]
self.acs[icao]['lon'] = latlon[1]
alt = pms.adsb.altitude(msg)
self.acs[icao]['alt'] = alt
output_buffer.append([t, icao, 'lat', latlon[0]])
output_buffer.append([t, icao, 'lon', latlon[1]])
output_buffer.append([t, icao, 'alt', alt])
local_updated_acs_buffer.append(icao)
# Uncertainty & accuracy
ac = self.acs[icao]
if 9 <= tc <= 18:
ac['nic_bc'] = pms.adsb.nic_b(msg)
if (5 <= tc <= 8) or (9 <= tc <= 18) or (20 <= tc <= 22):
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'])
elif (ac['ver'] == 2) and ('nic_a' in ac.keys()) and ('nic_bc' in ac.keys()):
ac['Rc'] = pms.adsb.nic_v2(msg, ac['nic_a'], ac['nic_bc'])
if tc == 19:
ac['HVE'], ac['VVE'] = pms.adsb.nuc_v(msg)
if ac['ver'] in [1, 2]:
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['EPU'], ac['VEPU'] = pms.adsb.nac_p(msg)
if tc == 31:
ac['ver'] = pms.adsb.version(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'])
if ac['ver'] == 1:
ac['nic_s'] = pms.adsb.nic_s(msg)
elif ac['ver'] == 2:
ac['nic_a'], ac['nic_bc'] = pms.adsb.nic_a_c(msg)
# process commb message
for t, msg in zip(commb_ts, commb_msgs):
icao = pms.icao(msg)
if icao not in self.acs:
continue
bds = pms.bds.infer(msg)
if bds == 'BDS50':
roll50 = pms.commb.roll50(msg)
trk50 = pms.commb.trk50(msg)
rtrk50 = pms.commb.rtrk50(msg)
gs50 = pms.commb.gs50(msg)
tas50 = pms.commb.tas50(msg)
self.acs[icao]['t50'] = t
if tas50:
self.acs[icao]['tas'] = tas50
output_buffer.append([t, icao, 'tas50', tas50])
if roll50:
self.acs[icao]['roll'] = roll50
output_buffer.append([t, icao, 'roll50', roll50])
if rtrk50:
self.acs[icao]['rtrk'] = rtrk50
output_buffer.append([t, icao, 'rtrk50', rtrk50])
if trk50:
output_buffer.append([t, icao, 'trk50', trk50])
if gs50:
output_buffer.append([t, icao, 'gs50', gs50])
elif bds == 'BDS60':
ias60 = pms.commb.ias60(msg)
hdg60 = pms.commb.hdg60(msg)
mach60 = pms.commb.mach60(msg)
roc60baro = pms.commb.vr60baro(msg)
roc60ins = pms.commb.vr60ins(msg)
if ias60 or hdg60 or mach60:
self.acs[icao]['t60'] = t
if ias60:
self.acs[icao]['ias'] = ias60
if hdg60:
self.acs[icao]['hdg'] = hdg60
if mach60:
self.acs[icao]['mach'] = mach60
if roc60baro:
output_buffer.append([t, icao, 'roc60baro', roc60baro])
if roc60ins:
output_buffer.append([t, icao, 'roc60ins', roc60ins])
# clear up old data
for icao in list(self.acs.keys()):
if self.t - self.acs[icao]['live'] > self.cache_timeout:
del self.acs[icao]
continue
if self.dumpto is not None:
dh = str(datetime.datetime.now().strftime("%Y%m%d_%H"))
fn = self.dumpto + '/pymodes_dump_%s.csv' % dh
output_buffer.sort(key=lambda x: x[0])
with open(fn, "a") as f:
writer = csv.writer(f)
writer.writerows(output_buffer)
return
def get_aircraft(self):
"""all aircraft that are stored in memeory"""
acs = self.acs
icaos = list(acs.keys())
for icao in icaos:
if acs[icao]['lat'] is None:
acs.pop(icao)
return acs

View File

@@ -6,7 +6,7 @@ https://github.com/pypa/sampleproject
Steps for deploying a new verison:
1. Increase the version number
2. remove the old deployment under [dist] folder
2. remove the old deployment under [dist] and [build] folder
3. run: python setup.py sdist
run: python setup.py bdist_wheel --universal
4. twine upload dist/*
@@ -30,7 +30,7 @@ setup(
# Versions should comply with PEP440. For a discussion on single-sourcing
# the version across setup.py and the project code, see
# https://packaging.python.org/en/latest/single_source_version.html
version="2.2",
version="2.4",
description="Python Mode-S and ADS-B Decoder",
long_description=long_description,
# The project's main homepage.
@@ -74,7 +74,7 @@ setup(
# your project is installed. For an analysis of "install_requires" vs pip's
# requirements files see:
# https://packaging.python.org/en/latest/requirements.html
install_requires=["numpy", "argparse"],
install_requires=["numpy", "argparse", "pyzmq", "pyrtlsdr"],
# List additional groups of dependencies here (e.g. development
# dependencies). You can install these using the following syntax,
# for example:

View File

@@ -4,10 +4,12 @@ from pyModeS import adsb, ehs
# === Decode sample data file ===
def adsb_decode_all(n=None):
print("===== Decode ADS-B sample data=====")
import csv
f = open('tests/data/sample_data_adsb.csv', 'rt')
f = open("tests/data/sample_data_adsb.csv", "rt")
msg0 = None
msg1 = None
@@ -37,5 +39,6 @@ def adsb_decode_all(n=None):
alt = adsb.altitude(m)
print(ts, m, icao, tc, pos, alt)
if __name__ == '__main__':
if __name__ == "__main__":
adsb_decode_all(n=100)

View File

@@ -3,12 +3,13 @@ from pyModeS import commb, common, bds
# === Decode sample data file ===
def bds_info(BDS, m):
if BDS == "BDS10":
info = [commb.ovc10(m)]
elif BDS == "BDS17":
info = ([i[-2:] for i in commb.cap17(m)])
info = [i[-2:] for i in commb.cap17(m)]
elif BDS == "BDS20":
info = [commb.cs20(m)]
@@ -20,13 +21,30 @@ def bds_info(BDS, m):
info = (commb.wind44(m), commb.temp44(m), commb.p44(m), commb.hum44(m))
elif BDS == "BDS44REV":
info = (commb.wind44(m, rev=True), commb.temp44(m, rev=True), commb.p44(m, rev=True), commb.hum44(m, rev=True))
info = (
commb.wind44(m, rev=True),
commb.temp44(m, rev=True),
commb.p44(m, rev=True),
commb.hum44(m, rev=True),
)
elif BDS == "BDS50":
info = (commb.roll50(m), commb.trk50(m), commb.gs50(m), commb.rtrk50(m), commb.tas50(m))
info = (
commb.roll50(m),
commb.trk50(m),
commb.gs50(m),
commb.rtrk50(m),
commb.tas50(m),
)
elif BDS == "BDS60":
info = (commb.hdg60(m), commb.ias60(m), commb.mach60(m), commb.vr60baro(m), commb.vr60ins(m))
info = (
commb.hdg60(m),
commb.ias60(m),
commb.mach60(m),
commb.vr60baro(m),
commb.vr60ins(m),
)
else:
info = None
@@ -39,8 +57,7 @@ def commb_decode_all(df, n=None):
print("===== Decode Comm-B sample data (DF=%s)=====" % df)
f = open('tests/data/sample_data_commb_df%s.csv' % df, 'rt')
f = open("tests/data/sample_data_commb_df%s.csv" % df, "rt")
for i, r in enumerate(csv.reader(f)):
if n and i > n:
@@ -55,21 +72,21 @@ def commb_decode_all(df, n=None):
code = common.altcode(m) if df == 20 else common.idcode(m)
if not BDS:
print(ts, m, icao, df, '%5s'%code, 'UNKNOWN')
print(ts, m, icao, df, "%5s" % code, "UNKNOWN")
continue
if len(BDS.split(",")) > 1:
print(ts, m, icao, df, '%5s' % code, end=' ')
print(ts, m, icao, df, "%5s" % code, end=" ")
for i, _bds in enumerate(BDS.split(",")):
if i == 0:
print(_bds, *bds_info(_bds, m))
else:
print(' ' * 55, _bds, *bds_info(_bds, m))
print(" " * 55, _bds, *bds_info(_bds, m))
else:
print(ts, m, icao, df, '%5s'%code, BDS, *bds_info(BDS, m))
print(ts, m, icao, df, "%5s" % code, BDS, *bds_info(BDS, m))
if __name__ == '__main__':
if __name__ == "__main__":
commb_decode_all(df=20, n=100)
commb_decode_all(df=21, n=100)

View File

@@ -2,12 +2,13 @@ from pyModeS import adsb
# === TEST ADS-B package ===
def test_adsb_icao():
assert adsb.icao("8D406B902015A678D4D220AA4BDA") == "406B90"
def test_adsb_category():
assert adsb.category("8D406B902015A678D4D220AA4BDA") == 5
assert adsb.category("8D406B902015A678D4D220AA4BDA") == 0
def test_adsb_callsign():
@@ -15,9 +16,22 @@ def test_adsb_callsign():
def test_adsb_position():
pos = adsb.position("8D40058B58C901375147EFD09357",
"8D40058B58C904A87F402D3B8C59",
1446332400, 1446332405)
pos = adsb.position(
"8D40058B58C901375147EFD09357",
"8D40058B58C904A87F402D3B8C59",
1446332400,
1446332405,
)
assert pos == (49.81755, 6.08442)
def test_adsb_position_swap_odd_even():
pos = adsb.position(
"8D40058B58C904A87F402D3B8C59",
"8D40058B58C901375147EFD09357",
1446332405,
1446332400,
)
assert pos == (49.81755, 6.08442)
@@ -29,27 +43,29 @@ def test_adsb_position_with_ref():
def test_adsb_airborne_position_with_ref():
pos = adsb.airborne_position_with_ref("8D40058B58C901375147EFD09357",
49.0, 6.0)
pos = adsb.airborne_position_with_ref("8D40058B58C901375147EFD09357", 49.0, 6.0)
assert pos == (49.82410, 6.06785)
pos = adsb.airborne_position_with_ref("8D40058B58C904A87F402D3B8C59",
49.0, 6.0)
pos = adsb.airborne_position_with_ref("8D40058B58C904A87F402D3B8C59", 49.0, 6.0)
assert pos == (49.81755, 6.08442)
def test_adsb_surface_position_with_ref():
pos = adsb.surface_position_with_ref("8FC8200A3AB8F5F893096B000000",
-43.5, 172.5)
pos = adsb.surface_position_with_ref("8FC8200A3AB8F5F893096B000000", -43.5, 172.5)
assert pos == (-43.48564, 172.53942)
def test_adsb_surface_position():
pos = adsb.surface_position("8CC8200A3AC8F009BCDEF2000000",
"8FC8200A3AB8F5F893096B000000",
0, 2,
-43.496, 172.558)
pos = adsb.surface_position(
"8CC8200A3AC8F009BCDEF2000000",
"8FC8200A3AB8F5F893096B000000",
0,
2,
-43.496,
172.558,
)
assert pos == (-43.48564, 172.53942)
def test_adsb_alt():
assert adsb.altitude("8D40058B58C901375147EFD09357") == 39000
@@ -58,10 +74,10 @@ def test_adsb_velocity():
vgs = adsb.velocity("8D485020994409940838175B284F")
vas = adsb.velocity("8DA05F219B06B6AF189400CBC33F")
vgs_surface = adsb.velocity("8FC8200A3AB8F5F893096B000000")
assert vgs == (159, 182.88, -832, 'GS')
assert vas == (375, 243.98, -2304, 'TAS')
assert vgs_surface == (19.0, 42.2, 0 , 'GS')
assert adsb.altitude_diff('8D485020994409940838175B284F') == 550
assert vgs == (159, 182.88, -832, "GS")
assert vas == (375, 243.98, -2304, "TAS")
assert vgs_surface == (19.0, 42.2, 0, "GS")
assert adsb.altitude_diff("8D485020994409940838175B284F") == 550
# def test_nic():

View File

@@ -1,20 +1,36 @@
from pyModeS import bds
def test_bds_infer():
assert bds.infer("8D406B902015A678D4D220AA4BDA") == 'BDS08'
assert bds.infer("8FC8200A3AB8F5F893096B000000") == 'BDS06'
assert bds.infer("8D40058B58C901375147EFD09357") == 'BDS05'
assert bds.infer("8D485020994409940838175B284F") == 'BDS09'
assert bds.infer("A800178D10010080F50000D5893C") == 'BDS10'
assert bds.infer("A0000638FA81C10000000081A92F") == 'BDS17'
assert bds.infer("A0001838201584F23468207CDFA5") == 'BDS20'
assert bds.infer("A0001839CA3800315800007448D9") == 'BDS40'
assert bds.infer("A000139381951536E024D4CCF6B5") == 'BDS50'
assert bds.infer("A00004128F39F91A7E27C46ADC21") == 'BDS60'
def test_bds_infer():
assert bds.infer("8D406B902015A678D4D220AA4BDA") == "BDS08"
assert bds.infer("8FC8200A3AB8F5F893096B000000") == "BDS06"
assert bds.infer("8D40058B58C901375147EFD09357") == "BDS05"
assert bds.infer("8D485020994409940838175B284F") == "BDS09"
assert bds.infer("A800178D10010080F50000D5893C") == "BDS10"
assert bds.infer("A0000638FA81C10000000081A92F") == "BDS17"
assert bds.infer("A0001838201584F23468207CDFA5") == "BDS20"
assert bds.infer("A0001839CA3800315800007448D9") == "BDS40"
assert bds.infer("A000139381951536E024D4CCF6B5") == "BDS50"
assert bds.infer("A00004128F39F91A7E27C46ADC21") == "BDS60"
def test_bds_is50or60():
assert bds.is50or60("A0001838201584F23468207CDFA5", 0, 0, 0) == None
assert bds.is50or60("A0000000FFDA9517000464000000", 182, 237, 1250) == 'BDS50'
assert bds.is50or60("A0000000919A5927E23444000000", 413, 54, 18700) == 'BDS60'
assert bds.is50or60("A0000000FFDA9517000464000000", 182, 237, 1250) == "BDS50"
assert bds.is50or60("A0000000919A5927E23444000000", 413, 54, 18700) == "BDS60"
def test_surface_position():
msg0 = "8FE48C033A9FA184B934E744C6FD"
msg1 = "8FE48C033A9FA68F7C3D39B1C2F0"
t0 = 1565608663102
t1 = 1565608666214
lat_ref = -23.4265448
lon_ref = -46.4816258
lat, lon = bds.bds06.surface_position(msg0, msg1, t0, t1, lat_ref, lon_ref)
assert abs(lon_ref - lon) < 0.05

View File

@@ -1,12 +1,14 @@
from pyModeS import bds, commb
# from pyModeS import ehs, els # deprecated
def test_bds20_callsign():
assert bds.bds20.cs20("A000083E202CC371C31DE0AA1CCF") == 'KLM1017_'
assert bds.bds20.cs20("A0001993202422F2E37CE038738E") == 'IBK2873_'
assert commb.cs20("A000083E202CC371C31DE0AA1CCF") == 'KLM1017_'
assert commb.cs20("A0001993202422F2E37CE038738E") == 'IBK2873_'
def test_bds20_callsign():
assert bds.bds20.cs20("A000083E202CC371C31DE0AA1CCF") == "KLM1017_"
assert bds.bds20.cs20("A0001993202422F2E37CE038738E") == "IBK2873_"
assert commb.cs20("A000083E202CC371C31DE0AA1CCF") == "KLM1017_"
assert commb.cs20("A0001993202422F2E37CE038738E") == "IBK2873_"
def test_bds40_functions():
@@ -21,14 +23,14 @@ def test_bds40_functions():
def test_bds50_functions():
assert bds.bds50.roll50("A000139381951536E024D4CCF6B5") == 2.1
assert bds.bds50.roll50("A0001691FFD263377FFCE02B2BF9") == -0.4 # signed value
assert bds.bds50.roll50("A0001691FFD263377FFCE02B2BF9") == -0.4 # signed value
assert bds.bds50.trk50("A000139381951536E024D4CCF6B5") == 114.258
assert bds.bds50.gs50("A000139381951536E024D4CCF6B5") == 438
assert bds.bds50.rtrk50("A000139381951536E024D4CCF6B5") == 0.125
assert bds.bds50.tas50("A000139381951536E024D4CCF6B5") == 424
assert commb.roll50("A000139381951536E024D4CCF6B5") == 2.1
assert commb.roll50("A0001691FFD263377FFCE02B2BF9") == -0.4 # signed value
assert commb.roll50("A0001691FFD263377FFCE02B2BF9") == -0.4 # signed value
assert commb.trk50("A000139381951536E024D4CCF6B5") == 114.258
assert commb.gs50("A000139381951536E024D4CCF6B5") == 438
assert commb.rtrk50("A000139381951536E024D4CCF6B5") == 0.125

View File

@@ -2,57 +2,63 @@ from pyModeS import common
def test_conversions():
assert common.hex2bin('6E406B') == "011011100100000001101011"
assert common.bin2hex('011011100100000001101011') == "6E406B"
assert common.hex2bin("6E406B") == "011011100100000001101011"
assert common.bin2hex("011011100100000001101011") == "6E406B"
assert common.int2hex(11160538) == "AA4BDA"
def test_crc_decode():
assert common.crc_legacy("8D406B902015A678D4D220AA4BDA") == 0
assert common.crc("8D406B902015A678D4D220AA4BDA") == 0
assert common.crc('8d8960ed58bf053cf11bc5932b7d') == 0
assert common.crc('8d45cab390c39509496ca9a32912') == 0
assert common.crc('8d49d3d4e1089d00000000744c3b') == 0
assert common.crc('8d74802958c904e6ef4ba0184d5c') == 0
assert common.crc('8d4400cd9b0000b4f87000e71a10') == 0
assert common.crc('8d4065de58a1054a7ef0218e226a') == 0
assert common.crc("8d8960ed58bf053cf11bc5932b7d") == 0
assert common.crc("8d45cab390c39509496ca9a32912") == 0
assert common.crc("8d49d3d4e1089d00000000744c3b") == 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
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 common.int2hex(parity) == "AA4BDA"
def test_icao():
assert common.icao("8D406B902015A678D4D220AA4BDA") == "406B90"
assert common.icao("A0001839CA3800315800007448D9") == '400940'
assert common.icao("A000139381951536E024D4CCF6B5") == '3C4DD2'
assert common.icao("A000029CFFBAA11E2004727281F1") == '4243D0'
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'
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
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

20
tests/test_tell.py Normal file
View File

@@ -0,0 +1,20 @@
from pyModeS.decoder import tell
messages = [
"8D406B902015A678D4D220AA4BDA",
"8FC8200A3AB8F5F893096B000000",
"8D40058B58C901375147EFD09357",
"8D485020994409940838175B284F",
"A000083E202CC371C31DE0AA1CCF",
"A8001E2520053332C1A820363386",
"A000029C85E42F313000007047D3",
"A5DC282C2A0108372CA6DA9693B0",
"A00015B8C26A00328400004242DA",
"A000139381951536E024D4CCF6B5",
"A00004128F39F91A7E27C46ADC21",
]
print("-" * 70)
for m in messages:
tell(m)
print("-" * 70)

View File

@@ -6,4 +6,6 @@ envlist = py2,py3
deps =
pytest
numpy
pyzmq
pyrtlsdr
commands = py.test