+39
-33
@@ -1,9 +1,7 @@
|
|||||||
The Python ADS-B/Mode-S Decoder
|
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,
|
@article{sun2019pymodes,
|
||||||
author={J. {Sun} and H. {V\^u} and J. {Ellerbroek} and J. M. {Hoekstra}},
|
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
|
Introduction
|
||||||
---------------------
|
---------------------
|
||||||
PyModeS is a Python library designed to decode Mode-S (including ADS-B) message.
|
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.
|
||||||
Message with following Downlink Formats (DF) are supported:
|
|
||||||
|
|
||||||
|
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=1-4 / BDS 0,8: Aircraft identification and category
|
||||||
- TC=5-8 / BDS 0,6: Surface position
|
- TC=5-8 / BDS 0,6: Surface position
|
||||||
@@ -53,13 +51,13 @@ Message with following Downlink Formats (DF) are supported:
|
|||||||
|
|
||||||
Resources
|
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
|
https://github.com/junzis/pyModeS
|
||||||
|
|
||||||
Detailed manual on Mode-S decoding is published at:
|
Detailed manual on Mode-S decoding is published at:
|
||||||
https://mode-s.org/decode.
|
https://mode-s.org/decode.
|
||||||
|
|
||||||
API documentation of pyModeS is at:
|
The API documentation of pyModeS is at:
|
||||||
http://pymodes.readthedocs.io
|
http://pymodes.readthedocs.io
|
||||||
|
|
||||||
|
|
||||||
@@ -67,7 +65,7 @@ http://pymodes.readthedocs.io
|
|||||||
Install
|
Install
|
||||||
-------
|
-------
|
||||||
|
|
||||||
To install latest version from the GitHub:
|
To install the latest version development from the GitHub:
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
@@ -82,29 +80,43 @@ To install the stable version (2.0) from pip:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
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:
|
arguments:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
--server SERVER server address or IP
|
--source SOURCE Choose data source, "rtlsdr" or "net"
|
||||||
--port PORT raw data port
|
--connect SERVER PORT DATATYPE
|
||||||
--rawtype RAWTYPE beast, avr or skysense
|
Define server, port and data type. Supported data
|
||||||
--latlon LAT LON receiver position
|
types are: ['raw', 'beast', 'skysense']
|
||||||
--show-uncertainty display uncertaint values, default off
|
--latlon LAT LON Receiver latitude and longitude, needed for the surface
|
||||||
--dumpto folder to dump decoded output
|
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 avr
|
||||||
|
$ modeslive --source net --connect 127.0.0.1 30005 beast
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
$ modesmixer2 --inSeriel port[:speed[:flow_control]] --outServer beast:[tcp_port]
|
|
||||||
|
|
||||||
Example screenshot:
|
Example screenshot:
|
||||||
|
|
||||||
@@ -164,11 +176,7 @@ Core functions for ADS-B decoding
|
|||||||
pms.adsb.airborne_velocity(msg)
|
pms.adsb.airborne_velocity(msg)
|
||||||
|
|
||||||
|
|
||||||
Note: When you have a fix position of the aircraft, it is convenient to
|
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.
|
||||||
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.
|
|
||||||
|
|
||||||
|
|
||||||
Decode altitude replies in DF4 / DF20
|
Decode altitude replies in DF4 / DF20
|
||||||
@@ -275,9 +283,7 @@ Meteorological hazard air report (MHR) [Experimental]
|
|||||||
|
|
||||||
Customize the streaming module
|
Customize the streaming module
|
||||||
******************************
|
******************************
|
||||||
The TCP client module from pyModeS can be re-used to stream and process Mode-S
|
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 ``BaseClient`` class to write your own logic to handle the messages.
|
||||||
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.
|
|
||||||
|
|
||||||
Here is an example:
|
Here is an example:
|
||||||
|
|
||||||
@@ -317,7 +323,7 @@ Here is an example:
|
|||||||
|
|
||||||
Unit test
|
Unit test
|
||||||
---------
|
---------
|
||||||
To perform unit tests. First install ``tox`` through pip, Then, run the following commands:
|
To perform unit tests. First, install ``tox`` through pip. Then, run the following commands:
|
||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,155 @@
|
|||||||
|
import numpy as np
|
||||||
|
import pyModeS as pms
|
||||||
|
from rtlsdr import RtlSdr
|
||||||
|
from threading import Thread
|
||||||
|
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(Thread):
|
||||||
|
def __init__(self, debug=False):
|
||||||
|
super(RtlReader, self).__init__()
|
||||||
|
self.signal_buffer = []
|
||||||
|
self.debug = debug
|
||||||
|
self.sdr = RtlSdr()
|
||||||
|
self.sdr.sample_rate = modes_sample_rate
|
||||||
|
self.sdr.center_freq = modes_frequency
|
||||||
|
self.sdr.gain = "auto"
|
||||||
|
# sdr.freq_correction = 75
|
||||||
|
|
||||||
|
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):
|
||||||
|
self.sdr.cancel_read_async()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
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__":
|
||||||
|
rtl = RtlReader()
|
||||||
|
rtl.debug = True
|
||||||
|
rtl.start()
|
||||||
|
rtl.join()
|
||||||
+76
-65
@@ -1,6 +1,6 @@
|
|||||||
'''
|
"""
|
||||||
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
|
from __future__ import print_function, division
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
@@ -9,47 +9,44 @@ import time
|
|||||||
import pyModeS as pms
|
import pyModeS as pms
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
import traceback
|
import traceback
|
||||||
|
import zmq
|
||||||
|
|
||||||
if (sys.version_info > (3, 0)):
|
if sys.version_info > (3, 0):
|
||||||
PY_VERSION = 3
|
PY_VERSION = 3
|
||||||
else:
|
else:
|
||||||
PY_VERSION = 2
|
PY_VERSION = 2
|
||||||
|
|
||||||
|
|
||||||
class BaseClient(Thread):
|
class BaseClient(Thread):
|
||||||
def __init__(self, host, port, rawtype):
|
def __init__(self, host, port, datatype):
|
||||||
Thread.__init__(self)
|
Thread.__init__(self)
|
||||||
self.host = host
|
self.host = host
|
||||||
self.port = port
|
self.port = port
|
||||||
self.buffer = []
|
self.buffer = []
|
||||||
self.rawtype = rawtype
|
self.socket = None
|
||||||
if self.rawtype not in ['avr', 'beast', 'skysense']:
|
self.datatype = datatype
|
||||||
print("rawtype must be either avr, beast or skysense")
|
if self.datatype not in ["raw", "beast", "skysense"]:
|
||||||
|
print("datatype must be either raw, beast or skysense")
|
||||||
os._exit(1)
|
os._exit(1)
|
||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
while True:
|
self.socket = zmq.Context().socket(zmq.STREAM)
|
||||||
try:
|
self.socket.setsockopt(zmq.LINGER, 0)
|
||||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
self.socket.setsockopt(zmq.RCVTIMEO, 2000)
|
||||||
s.settimeout(10) # 10 second timeout
|
self.socket.connect("tcp://%s:%s" % (self.host, self.port))
|
||||||
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)
|
|
||||||
|
|
||||||
|
def read_raw_buffer(self):
|
||||||
|
""" Read raw ADS-B data type.
|
||||||
|
|
||||||
def read_avr_buffer(self):
|
String strats with "*" and ends with ";". For example:
|
||||||
# -- testing --
|
*5d484ba898f8c6;
|
||||||
# for b in self.buffer:
|
*8d400cd5990d7e9a10043e5e6da0;
|
||||||
# print(chr(b), b)
|
*a0001498be800030aa0000c7a75f;
|
||||||
|
"""
|
||||||
# Append message with 0-9,A-F,a-f, until stop sign
|
|
||||||
|
|
||||||
messages = []
|
messages = []
|
||||||
|
|
||||||
msg_stop = False
|
msg_stop = False
|
||||||
|
self.current_msg = ""
|
||||||
for b in self.buffer:
|
for b in self.buffer:
|
||||||
if b == 59:
|
if b == 59:
|
||||||
msg_stop = True
|
msg_stop = True
|
||||||
@@ -57,9 +54,11 @@ class BaseClient(Thread):
|
|||||||
messages.append([self.current_msg, ts])
|
messages.append([self.current_msg, ts])
|
||||||
if b == 42:
|
if b == 42:
|
||||||
msg_stop = False
|
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.current_msg = self.current_msg + chr(b)
|
||||||
|
|
||||||
self.buffer = []
|
self.buffer = []
|
||||||
@@ -67,7 +66,8 @@ class BaseClient(Thread):
|
|||||||
return messages
|
return messages
|
||||||
|
|
||||||
def read_beast_buffer(self):
|
def read_beast_buffer(self):
|
||||||
'''
|
"""Handle mode-s beast data type.
|
||||||
|
|
||||||
<esc> "1" : 6 byte MLAT timestamp, 1 byte signal level,
|
<esc> "1" : 6 byte MLAT timestamp, 1 byte signal level,
|
||||||
2 byte Mode-AC
|
2 byte Mode-AC
|
||||||
<esc> "2" : 6 byte MLAT timestamp, 1 byte signal level,
|
<esc> "2" : 6 byte MLAT timestamp, 1 byte signal level,
|
||||||
@@ -81,8 +81,7 @@ class BaseClient(Thread):
|
|||||||
|
|
||||||
timestamp:
|
timestamp:
|
||||||
wiki.modesbeast.com/Radarcape:Firmware_Versions#The_GPS_timestamp
|
wiki.modesbeast.com/Radarcape:Firmware_Versions#The_GPS_timestamp
|
||||||
'''
|
"""
|
||||||
|
|
||||||
messages_mlat = []
|
messages_mlat = []
|
||||||
msg = []
|
msg = []
|
||||||
i = 0
|
i = 0
|
||||||
@@ -91,16 +90,16 @@ class BaseClient(Thread):
|
|||||||
# then, reset the self.buffer with the remainder
|
# then, reset the self.buffer with the remainder
|
||||||
|
|
||||||
while i < len(self.buffer):
|
while i < len(self.buffer):
|
||||||
if (self.buffer[i:i+2] == [0x1a, 0x1a]):
|
if self.buffer[i : i + 2] == [0x1A, 0x1A]:
|
||||||
msg.append(0x1a)
|
msg.append(0x1A)
|
||||||
i += 1
|
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
|
# special case where the last bit is 0x1a
|
||||||
msg.append(0x1a)
|
msg.append(0x1A)
|
||||||
elif self.buffer[i] == 0x1a:
|
elif self.buffer[i] == 0x1A:
|
||||||
if i == len(self.buffer) - 1:
|
if i == len(self.buffer) - 1:
|
||||||
# special case where the last bit is 0x1a
|
# special case where the last bit is 0x1a
|
||||||
msg.append(0x1a)
|
msg.append(0x1A)
|
||||||
elif len(msg) > 0:
|
elif len(msg) > 0:
|
||||||
messages_mlat.append(msg)
|
messages_mlat.append(msg)
|
||||||
msg = []
|
msg = []
|
||||||
@@ -112,12 +111,12 @@ class BaseClient(Thread):
|
|||||||
if len(msg) > 0:
|
if len(msg) > 0:
|
||||||
reminder = []
|
reminder = []
|
||||||
for i, m in enumerate(msg):
|
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
|
# rewind 0x1a, except when it is at the last bit
|
||||||
reminder.extend([m, m])
|
reminder.extend([m, m])
|
||||||
else:
|
else:
|
||||||
reminder.append(m)
|
reminder.append(m)
|
||||||
self.buffer = [0x1a] + msg
|
self.buffer = [0x1A] + msg
|
||||||
else:
|
else:
|
||||||
self.buffer = []
|
self.buffer = []
|
||||||
|
|
||||||
@@ -131,10 +130,10 @@ class BaseClient(Thread):
|
|||||||
|
|
||||||
if msgtype == 0x32:
|
if msgtype == 0x32:
|
||||||
# Mode-S Short Message, 7 byte, 14-len hexstr
|
# 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:
|
elif msgtype == 0x33:
|
||||||
# Mode-S Long Message, 14 byte, 28-len hexstr
|
# 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:
|
else:
|
||||||
# Other message tupe
|
# Other message tupe
|
||||||
continue
|
continue
|
||||||
@@ -215,25 +214,37 @@ class BaseClient(Thread):
|
|||||||
messages = []
|
messages = []
|
||||||
while len(self.buffer) > SS_MSGLENGTH:
|
while len(self.buffer) > SS_MSGLENGTH:
|
||||||
i = 0
|
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
|
i += 1
|
||||||
if (self.buffer[i]>>7):
|
if self.buffer[i] >> 7:
|
||||||
#Long message
|
# Long message
|
||||||
payload = self.buffer[i:i+14]
|
payload = self.buffer[i : i + 14]
|
||||||
else:
|
else:
|
||||||
#Short message
|
# Short message
|
||||||
payload = self.buffer[i:i+7]
|
payload = self.buffer[i : i + 7]
|
||||||
msg = ''.join('%02X' % j for j in payload)
|
msg = "".join("%02X" % j for j in payload)
|
||||||
i += 14 #Both message types use 14 bytes
|
i += 14 # Both message types use 14 bytes
|
||||||
tsbin = self.buffer[i:i+6]
|
tsbin = self.buffer[i : i + 6]
|
||||||
sec = ( (tsbin[0] & 0x7f) << 10) | (tsbin[1] << 2 ) | (tsbin[2] >> 6)
|
sec = (
|
||||||
nano = ( (tsbin[2] & 0x3f) << 24) | (tsbin[3] << 16) | (tsbin[4] << 8) | tsbin[5]
|
((tsbin[0] & 0x7F) << 10)
|
||||||
ts = sec + nano*1.0e-9
|
| (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
|
i += 6
|
||||||
#Signal and noise level - Don't care for now
|
# Signal and noise level - Don't care for now
|
||||||
i += 3
|
i += 3
|
||||||
self.buffer = self.buffer[SS_MSGLENGTH:]
|
self.buffer = self.buffer[SS_MSGLENGTH:]
|
||||||
messages.append( [msg,ts] )
|
messages.append([msg, ts])
|
||||||
else:
|
else:
|
||||||
self.buffer = self.buffer[1:]
|
self.buffer = self.buffer[1:]
|
||||||
return messages
|
return messages
|
||||||
@@ -244,11 +255,11 @@ class BaseClient(Thread):
|
|||||||
print("%15.9f %s" % (t, msg))
|
print("%15.9f %s" % (t, msg))
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
sock = self.connect()
|
self.connect()
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
received = sock.recv(1024)
|
received = [i for i in self.socket.recv(4096)]
|
||||||
|
|
||||||
if PY_VERSION == 2:
|
if PY_VERSION == 2:
|
||||||
received = [ord(i) for i in received]
|
received = [ord(i) for i in received]
|
||||||
@@ -261,11 +272,11 @@ class BaseClient(Thread):
|
|||||||
# continue
|
# continue
|
||||||
# -- Removed!! Cause delay in low data rate scenario --
|
# -- Removed!! Cause delay in low data rate scenario --
|
||||||
|
|
||||||
if self.rawtype == 'beast':
|
if self.datatype == "beast":
|
||||||
messages = self.read_beast_buffer()
|
messages = self.read_beast_buffer()
|
||||||
elif self.rawtype == 'avr':
|
elif self.datatype == "raw":
|
||||||
messages = self.read_avr_buffer()
|
messages = self.read_raw_buffer()
|
||||||
elif self.rawtype == 'skysense':
|
elif self.datatype == "skysense":
|
||||||
messages = self.read_skysense_buffer()
|
messages = self.read_skysense_buffer()
|
||||||
|
|
||||||
if not messages:
|
if not messages:
|
||||||
@@ -279,8 +290,8 @@ class BaseClient(Thread):
|
|||||||
# Provides the user an option to supply the environment
|
# Provides the user an option to supply the environment
|
||||||
# variable PYMODES_DEBUG to halt the execution
|
# variable PYMODES_DEBUG to halt the execution
|
||||||
# for debugging purposes
|
# for debugging purposes
|
||||||
debug_intent = os.environ.get('PYMODES_DEBUG', 'false')
|
debug_intent = os.environ.get("PYMODES_DEBUG", "false")
|
||||||
if debug_intent.lower() == 'true':
|
if debug_intent.lower() == "true":
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
sys.exit()
|
sys.exit()
|
||||||
else:
|
else:
|
||||||
@@ -292,11 +303,11 @@ class BaseClient(Thread):
|
|||||||
print("Unexpected Error:", e)
|
print("Unexpected Error:", e)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
# for testing purpose only
|
# for testing purpose only
|
||||||
host = sys.argv[1]
|
host = sys.argv[1]
|
||||||
port = int(sys.argv[2])
|
port = int(sys.argv[2])
|
||||||
rawtype = sys.argv[3]
|
datatype = sys.argv[3]
|
||||||
client = BaseClient(host=host, port=port, rawtype=rawtype)
|
client = BaseClient(host=host, port=port, datatype=datatype)
|
||||||
client.daemon = True
|
client.daemon = True
|
||||||
client.run()
|
client.run()
|
||||||
|
|||||||
+113
-23
@@ -9,6 +9,7 @@ import curses
|
|||||||
from threading import Lock
|
from threading import Lock
|
||||||
import pyModeS as pms
|
import pyModeS as pms
|
||||||
from pyModeS.extra.tcpclient import BaseClient
|
from pyModeS.extra.tcpclient import BaseClient
|
||||||
|
from pyModeS.extra.rtlreader import RtlReader
|
||||||
from pyModeS.streamer.stream import Stream
|
from pyModeS.streamer.stream import Stream
|
||||||
from pyModeS.streamer.screen import Screen
|
from pyModeS.streamer.screen import Screen
|
||||||
|
|
||||||
@@ -18,30 +19,77 @@ ADSB_TS = []
|
|||||||
COMMB_MSG = []
|
COMMB_MSG = []
|
||||||
COMMB_TS = []
|
COMMB_TS = []
|
||||||
|
|
||||||
|
support_rawtypes = ["raw", "beast", "skysense"]
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('--server', help='server address or IP', required=True)
|
parser.add_argument(
|
||||||
parser.add_argument('--port', help='raw data port', required=True)
|
"--source",
|
||||||
parser.add_argument('--rawtype', help='beast, avr or skysense', required=True)
|
help='Choose data source, "rtlsdr" or "net"',
|
||||||
parser.add_argument('--latlon', help='receiver position', nargs=2, metavar=('LAT', 'LON'), required=True)
|
required=True,
|
||||||
parser.add_argument('--show-uncertainty', dest='uncertainty', help='display uncertaint values, default off', action='store_true', required=False, default=False)
|
default="net",
|
||||||
parser.add_argument('--dumpto', help='folder to dump decoded output', required=False, default=None)
|
)
|
||||||
|
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()
|
args = parser.parse_args()
|
||||||
|
|
||||||
SERVER = args.server
|
SOURCE = args.source
|
||||||
PORT = int(args.port)
|
LATLON = args.latlon
|
||||||
RAWTYPE = args.rawtype
|
|
||||||
LAT0 = float(args.latlon[0])
|
|
||||||
LON0 = float(args.latlon[1])
|
|
||||||
UNCERTAINTY = args.uncertainty
|
UNCERTAINTY = args.uncertainty
|
||||||
DUMPTO = args.dumpto
|
DUMPTO = args.dumpto
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
print('Source must be "rtlsdr" or "net".')
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
if DUMPTO is not None:
|
if DUMPTO is not None:
|
||||||
# append to current folder except root is given
|
# append to current folder except root is given
|
||||||
if DUMPTO[0] != '/':
|
if DUMPTO[0] != "/":
|
||||||
DUMPTO = os.getcwd() + '/' + DUMPTO
|
DUMPTO = os.getcwd() + "/" + DUMPTO
|
||||||
|
|
||||||
if not os.path.isdir(DUMPTO):
|
if not os.path.isdir(DUMPTO):
|
||||||
print('Error: dump folder (%s) does not exist' % DUMPTO)
|
print("Error: dump folder (%s) does not exist" % DUMPTO)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
@@ -56,7 +104,7 @@ class ModesClient(BaseClient):
|
|||||||
local_buffer_ehs_ts = []
|
local_buffer_ehs_ts = []
|
||||||
|
|
||||||
for msg, t in messages:
|
for msg, t in messages:
|
||||||
if len(msg) < 28: # only process long messages
|
if len(msg) < 28: # only process long messages
|
||||||
continue
|
continue
|
||||||
|
|
||||||
df = pms.df(msg)
|
df = pms.df(msg)
|
||||||
@@ -70,7 +118,6 @@ class ModesClient(BaseClient):
|
|||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
||||||
LOCK.acquire()
|
LOCK.acquire()
|
||||||
ADSB_MSG.extend(local_buffer_adsb_msg)
|
ADSB_MSG.extend(local_buffer_adsb_msg)
|
||||||
ADSB_TS.extend(local_buffer_adsb_ts)
|
ADSB_TS.extend(local_buffer_adsb_ts)
|
||||||
@@ -79,14 +126,57 @@ class ModesClient(BaseClient):
|
|||||||
LOCK.release()
|
LOCK.release()
|
||||||
|
|
||||||
|
|
||||||
|
class ModesRtlReader(RtlReader):
|
||||||
|
"""docstring for ModesRtlReader."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(ModesRtlReader, self).__init__()
|
||||||
|
|
||||||
|
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)
|
||||||
|
# print(len(ADSB_MSG))
|
||||||
|
# print(len(COMMB_MSG))
|
||||||
|
LOCK.release()
|
||||||
|
|
||||||
|
|
||||||
# redirect all stdout to null, avoiding messing up with the screen
|
# 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)
|
if SOURCE == "net":
|
||||||
client.daemon = True
|
client = ModesClient(host=SERVER, port=PORT, rawtype=DATATYPE)
|
||||||
client.start()
|
client.daemon = True
|
||||||
|
client.start()
|
||||||
|
elif SOURCE == "rtlsdr":
|
||||||
|
rtl = ModesRtlReader()
|
||||||
|
# rtl.debug = True
|
||||||
|
rtl.daemon = True
|
||||||
|
rtl.start()
|
||||||
|
|
||||||
stream = Stream(lat0=LAT0, lon0=LON0, dumpto=DUMPTO)
|
stream = Stream(latlon=LATLON, dumpto=DUMPTO)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
screen = Screen(uncertainty=UNCERTAINTY)
|
screen = Screen(uncertainty=UNCERTAINTY)
|
||||||
@@ -94,7 +184,7 @@ try:
|
|||||||
screen.start()
|
screen.start()
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
if len(ADSB_MSG) > 200:
|
if len(ADSB_MSG) > 1:
|
||||||
LOCK.acquire()
|
LOCK.acquire()
|
||||||
stream.process_raw(ADSB_TS, ADSB_MSG, COMMB_TS, COMMB_MSG)
|
stream.process_raw(ADSB_TS, ADSB_MSG, COMMB_TS, COMMB_MSG)
|
||||||
ADSB_MSG = []
|
ADSB_MSG = []
|
||||||
@@ -107,7 +197,7 @@ try:
|
|||||||
try:
|
try:
|
||||||
screen.update_data(acs)
|
screen.update_data(acs)
|
||||||
screen.update()
|
screen.update()
|
||||||
time.sleep(0.02)
|
time.sleep(1)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
raise
|
raise
|
||||||
except:
|
except:
|
||||||
|
|||||||
+55
-49
@@ -6,38 +6,39 @@ import time
|
|||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
COLUMNS = [
|
COLUMNS = [
|
||||||
('call', 10),
|
("call", 10),
|
||||||
('lat', 10),
|
("lat", 10),
|
||||||
('lon', 10),
|
("lon", 10),
|
||||||
('alt', 7),
|
("alt", 7),
|
||||||
('gs', 5),
|
("gs", 5),
|
||||||
('tas', 5),
|
("tas", 5),
|
||||||
('ias', 5),
|
("ias", 5),
|
||||||
('mach', 7),
|
("mach", 7),
|
||||||
('roc', 7),
|
("roc", 7),
|
||||||
('trk', 10),
|
("trk", 10),
|
||||||
('hdg', 10),
|
("hdg", 10),
|
||||||
('live', 6),
|
("live", 6),
|
||||||
]
|
]
|
||||||
|
|
||||||
UNCERTAINTY_COLUMNS = [
|
UNCERTAINTY_COLUMNS = [
|
||||||
('|', 5),
|
("|", 5),
|
||||||
('ver', 4),
|
("ver", 4),
|
||||||
('HPL', 5),
|
("HPL", 5),
|
||||||
('RCu', 5),
|
("RCu", 5),
|
||||||
('RCv', 5),
|
("RCv", 5),
|
||||||
('HVE', 5),
|
("HVE", 5),
|
||||||
('VVE', 5),
|
("VVE", 5),
|
||||||
('Rc', 4),
|
("Rc", 4),
|
||||||
('VPL', 5),
|
("VPL", 5),
|
||||||
('EPU', 5),
|
("EPU", 5),
|
||||||
('VEPU', 6),
|
("VEPU", 6),
|
||||||
('HFOMr', 7),
|
("HFOMr", 7),
|
||||||
('VFOMr', 7),
|
("VFOMr", 7),
|
||||||
('PE_RCu', 8),
|
("PE_RCu", 8),
|
||||||
('PE_VPL', 8),
|
("PE_VPL", 8),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class Screen(Thread):
|
class Screen(Thread):
|
||||||
def __init__(self, uncertainty=False):
|
def __init__(self, uncertainty=False):
|
||||||
Thread.__init__(self)
|
Thread.__init__(self)
|
||||||
@@ -55,7 +56,6 @@ class Screen(Thread):
|
|||||||
if uncertainty:
|
if uncertainty:
|
||||||
self.columns.extend(UNCERTAINTY_COLUMNS)
|
self.columns.extend(UNCERTAINTY_COLUMNS)
|
||||||
|
|
||||||
|
|
||||||
def reset_cursor_pos(self):
|
def reset_cursor_pos(self):
|
||||||
self.screen.move(self.y, self.x)
|
self.screen.move(self.y, self.x)
|
||||||
|
|
||||||
@@ -64,7 +64,12 @@ class Screen(Thread):
|
|||||||
|
|
||||||
def draw_frame(self):
|
def draw_frame(self):
|
||||||
self.screen.border(0)
|
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):
|
def update(self):
|
||||||
if len(self.acs) == 0:
|
if len(self.acs) == 0:
|
||||||
@@ -81,21 +86,20 @@ class Screen(Thread):
|
|||||||
|
|
||||||
row = 1
|
row = 1
|
||||||
|
|
||||||
header = ' icao'
|
header = " icao"
|
||||||
for c, cw in self.columns:
|
for c, cw in self.columns:
|
||||||
header += (cw-len(c))*' ' + c
|
header += (cw - len(c)) * " " + c
|
||||||
|
|
||||||
# fill end with spaces
|
# 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:
|
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)
|
self.screen.addstr(row, 1, header)
|
||||||
|
|
||||||
row +=1
|
row += 1
|
||||||
self.screen.addstr(row, 1, '-'*(self.scr_w-2))
|
self.screen.addstr(row, 1, "-" * (self.scr_w - 2))
|
||||||
|
|
||||||
icaos = np.array(list(self.acs.keys()))
|
icaos = np.array(list(self.acs.keys()))
|
||||||
icaos = np.sort(icaos)
|
icaos = np.sort(icaos)
|
||||||
@@ -105,10 +109,10 @@ class Screen(Thread):
|
|||||||
idx = row + self.offset - 3
|
idx = row + self.offset - 3
|
||||||
|
|
||||||
if idx > len(icaos) - 1:
|
if idx > len(icaos) - 1:
|
||||||
line = ' '*(self.scr_w-2)
|
line = " " * (self.scr_w - 2)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
line = ''
|
line = ""
|
||||||
|
|
||||||
icao = icaos[idx]
|
icao = icaos[idx]
|
||||||
ac = self.acs[icao]
|
ac = self.acs[icao]
|
||||||
@@ -116,22 +120,22 @@ class Screen(Thread):
|
|||||||
line += icao
|
line += icao
|
||||||
|
|
||||||
for c, cw in self.columns:
|
for c, cw in self.columns:
|
||||||
if c=='|':
|
if c == "|":
|
||||||
val = '|'
|
val = "|"
|
||||||
elif c=='live':
|
elif c == "live":
|
||||||
val = str(int(time.time() - ac[c]))+'s'
|
val = str(ac[c] - int(time.time())) + "s"
|
||||||
elif ac[c] is None:
|
elif ac[c] is None:
|
||||||
val = ''
|
val = ""
|
||||||
else:
|
else:
|
||||||
val = ac[c]
|
val = ac[c]
|
||||||
val_str = str(val)
|
val_str = str(val)
|
||||||
line += (cw-len(val_str))*' ' + val_str
|
line += (cw - len(val_str)) * " " + val_str
|
||||||
|
|
||||||
# fill end with spaces
|
# 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:
|
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):
|
if (icao is not None) and (self.lock_icao == icao):
|
||||||
self.screen.addstr(row, 1, line, curses.A_STANDOUT)
|
self.screen.addstr(row, 1, line, curses.A_STANDOUT)
|
||||||
@@ -140,11 +144,13 @@ class Screen(Thread):
|
|||||||
else:
|
else:
|
||||||
self.screen.addstr(row, 1, line)
|
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
|
total_page = len(icaos) // (self.scr_h - 4) + 1
|
||||||
current_page = self.offset // (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()
|
self.reset_cursor_pos()
|
||||||
|
|
||||||
@@ -168,7 +174,7 @@ class Screen(Thread):
|
|||||||
self.offset = offset_intent
|
self.offset = offset_intent
|
||||||
else:
|
else:
|
||||||
self.offset = 0
|
self.offset = 0
|
||||||
elif c == curses.KEY_DOWN :
|
elif c == curses.KEY_DOWN:
|
||||||
y_intent = self.y + 1
|
y_intent = self.y + 1
|
||||||
if y_intent < self.scr_h - 3:
|
if y_intent < self.scr_h - 3:
|
||||||
self.y = y_intent
|
self.y = y_intent
|
||||||
|
|||||||
+121
-108
@@ -5,24 +5,27 @@ import datetime
|
|||||||
import csv
|
import csv
|
||||||
import pyModeS as pms
|
import pyModeS as pms
|
||||||
|
|
||||||
class Stream():
|
|
||||||
def __init__(self, lat0, lon0, dumpto=None):
|
class Stream:
|
||||||
|
def __init__(self, latlon=None, dumpto=None):
|
||||||
|
|
||||||
self.acs = dict()
|
self.acs = dict()
|
||||||
|
|
||||||
self.lat0 = lat0
|
if latlon is not None:
|
||||||
self.lon0 = lon0
|
self.lat0 = float(latlon[0])
|
||||||
|
self.lon0 = float(latlon[1])
|
||||||
|
else:
|
||||||
|
self.lat0 = None
|
||||||
|
self.lon0 = None
|
||||||
|
|
||||||
self.t = 0
|
self.t = 0
|
||||||
self.cache_timeout = 60 # seconds
|
self.cache_timeout = 60 # seconds
|
||||||
|
|
||||||
|
|
||||||
if dumpto is not None and os.path.isdir(dumpto):
|
if dumpto is not None and os.path.isdir(dumpto):
|
||||||
self.dumpto = dumpto
|
self.dumpto = dumpto
|
||||||
else:
|
else:
|
||||||
self.dumpto = None
|
self.dumpto = None
|
||||||
|
|
||||||
|
|
||||||
def process_raw(self, adsb_ts, adsb_msgs, commb_ts, commb_msgs, tnow=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
|
"""process a chunk of adsb and commb messages recieved in the same
|
||||||
time period.
|
time period.
|
||||||
@@ -42,43 +45,43 @@ class Stream():
|
|||||||
|
|
||||||
if icao not in self.acs:
|
if icao not in self.acs:
|
||||||
self.acs[icao] = {
|
self.acs[icao] = {
|
||||||
'live': None,
|
"live": None,
|
||||||
'call': None,
|
"call": None,
|
||||||
'lat': None,
|
"lat": None,
|
||||||
'lon': None,
|
"lon": None,
|
||||||
'alt': None,
|
"alt": None,
|
||||||
'gs': None,
|
"gs": None,
|
||||||
'trk': None,
|
"trk": None,
|
||||||
'roc': None,
|
"roc": None,
|
||||||
'tas': None,
|
"tas": None,
|
||||||
'roll': None,
|
"roll": None,
|
||||||
'rtrk': None,
|
"rtrk": None,
|
||||||
'ias': None,
|
"ias": None,
|
||||||
'mach': None,
|
"mach": None,
|
||||||
'hdg': None,
|
"hdg": None,
|
||||||
'ver' : None,
|
"ver": None,
|
||||||
'HPL' : None,
|
"HPL": None,
|
||||||
'RCu' : None,
|
"RCu": None,
|
||||||
'RCv' : None,
|
"RCv": None,
|
||||||
'HVE' : None,
|
"HVE": None,
|
||||||
'VVE' : None,
|
"VVE": None,
|
||||||
'Rc' : None,
|
"Rc": None,
|
||||||
'VPL' : None,
|
"VPL": None,
|
||||||
'EPU' : None,
|
"EPU": None,
|
||||||
'VEPU' : None,
|
"VEPU": None,
|
||||||
'HFOMr' : None,
|
"HFOMr": None,
|
||||||
'VFOMr' : None,
|
"VFOMr": None,
|
||||||
'PE_RCu' : None,
|
"PE_RCu": None,
|
||||||
'PE_VPL' : None,
|
"PE_VPL": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
self.acs[icao]['t'] = t
|
self.acs[icao]["t"] = t
|
||||||
self.acs[icao]['live'] = int(t)
|
self.acs[icao]["live"] = int(t)
|
||||||
|
|
||||||
if 1 <= tc <= 4:
|
if 1 <= tc <= 4:
|
||||||
cs = pms.adsb.callsign(msg)
|
cs = pms.adsb.callsign(msg)
|
||||||
self.acs[icao]['call'] = cs
|
self.acs[icao]["call"] = cs
|
||||||
output_buffer.append([t, icao, 'cs', cs])
|
output_buffer.append([t, icao, "cs", cs])
|
||||||
|
|
||||||
if (5 <= tc <= 8) or (tc == 19):
|
if (5 <= tc <= 8) or (tc == 19):
|
||||||
vdata = pms.adsb.velocity(msg)
|
vdata = pms.adsb.velocity(msg)
|
||||||
@@ -86,42 +89,47 @@ class Stream():
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
spd, trk, roc, tag = vdata
|
spd, trk, roc, tag = vdata
|
||||||
if tag != 'GS':
|
if tag != "GS":
|
||||||
continue
|
continue
|
||||||
if (spd is None) or (trk is None):
|
if (spd is None) or (trk is None):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
self.acs[icao]['gs'] = spd
|
self.acs[icao]["gs"] = spd
|
||||||
self.acs[icao]['trk'] = trk
|
self.acs[icao]["trk"] = trk
|
||||||
self.acs[icao]['roc'] = roc
|
self.acs[icao]["roc"] = roc
|
||||||
self.acs[icao]['tv'] = t
|
self.acs[icao]["tv"] = t
|
||||||
|
|
||||||
output_buffer.append([t, icao, 'gs', spd])
|
output_buffer.append([t, icao, "gs", spd])
|
||||||
output_buffer.append([t, icao, 'trk', trk])
|
output_buffer.append([t, icao, "trk", trk])
|
||||||
output_buffer.append([t, icao, 'roc', roc])
|
output_buffer.append([t, icao, "roc", roc])
|
||||||
|
|
||||||
|
if 5 <= tc <= 18:
|
||||||
if (5 <= tc <= 18):
|
|
||||||
oe = pms.adsb.oe_flag(msg)
|
oe = pms.adsb.oe_flag(msg)
|
||||||
self.acs[icao][oe] = msg
|
self.acs[icao][oe] = msg
|
||||||
self.acs[icao]['t'+str(oe)] = t
|
self.acs[icao]["t" + str(oe)] = t
|
||||||
|
|
||||||
if ('tpos' in self.acs[icao]) and (t - self.acs[icao]['tpos'] < 180):
|
if ("tpos" in self.acs[icao]) and (
|
||||||
|
t - self.acs[icao]["tpos"] < 180
|
||||||
|
):
|
||||||
# use single message decoding
|
# use single message decoding
|
||||||
rlat = self.acs[icao]['lat']
|
rlat = self.acs[icao]["lat"]
|
||||||
rlon = self.acs[icao]['lon']
|
rlon = self.acs[icao]["lon"]
|
||||||
latlon = pms.adsb.position_with_ref(msg, rlat, rlon)
|
latlon = pms.adsb.position_with_ref(msg, rlat, rlon)
|
||||||
elif ('t0' in self.acs[icao]) and ('t1' in self.acs[icao]) and \
|
elif (
|
||||||
(abs(self.acs[icao]['t0'] - self.acs[icao]['t1']) < 10):
|
("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
|
# use multi message decoding
|
||||||
try:
|
try:
|
||||||
latlon = pms.adsb.position(
|
latlon = pms.adsb.position(
|
||||||
self.acs[icao][0],
|
self.acs[icao][0],
|
||||||
self.acs[icao][1],
|
self.acs[icao][1],
|
||||||
self.acs[icao]['t0'],
|
self.acs[icao]["t0"],
|
||||||
self.acs[icao]['t1'],
|
self.acs[icao]["t1"],
|
||||||
self.lat0, self.lon0
|
self.lat0,
|
||||||
)
|
self.lon0,
|
||||||
|
)
|
||||||
except:
|
except:
|
||||||
# mix of surface and airborne position message
|
# mix of surface and airborne position message
|
||||||
continue
|
continue
|
||||||
@@ -129,16 +137,16 @@ class Stream():
|
|||||||
latlon = None
|
latlon = None
|
||||||
|
|
||||||
if latlon is not None:
|
if latlon is not None:
|
||||||
self.acs[icao]['tpos'] = t
|
self.acs[icao]["tpos"] = t
|
||||||
self.acs[icao]['lat'] = latlon[0]
|
self.acs[icao]["lat"] = latlon[0]
|
||||||
self.acs[icao]['lon'] = latlon[1]
|
self.acs[icao]["lon"] = latlon[1]
|
||||||
|
|
||||||
alt = pms.adsb.altitude(msg)
|
alt = pms.adsb.altitude(msg)
|
||||||
self.acs[icao]['alt'] = alt
|
self.acs[icao]["alt"] = alt
|
||||||
|
|
||||||
output_buffer.append([t, icao, 'lat', latlon[0]])
|
output_buffer.append([t, icao, "lat", latlon[0]])
|
||||||
output_buffer.append([t, icao, 'lon', latlon[1]])
|
output_buffer.append([t, icao, "lon", latlon[1]])
|
||||||
output_buffer.append([t, icao, 'alt', alt])
|
output_buffer.append([t, icao, "alt", alt])
|
||||||
|
|
||||||
local_updated_acs_buffer.append(icao)
|
local_updated_acs_buffer.append(icao)
|
||||||
|
|
||||||
@@ -146,35 +154,42 @@ class Stream():
|
|||||||
ac = self.acs[icao]
|
ac = self.acs[icao]
|
||||||
|
|
||||||
if 9 <= tc <= 18:
|
if 9 <= tc <= 18:
|
||||||
ac['nic_bc'] = pms.adsb.nic_b(msg)
|
ac["nic_bc"] = pms.adsb.nic_b(msg)
|
||||||
|
|
||||||
if (5 <= tc <= 8) or (9 <= tc <= 18) or (20 <= tc <= 22):
|
if (5 <= tc <= 8) or (9 <= tc <= 18) or (20 <= tc <= 22):
|
||||||
ac['HPL'], ac['RCu'], ac['RCv'] = pms.adsb.nuc_p(msg)
|
ac["HPL"], ac["RCu"], ac["RCv"] = pms.adsb.nuc_p(msg)
|
||||||
|
|
||||||
if (ac['ver'] == 1) and ('nic_s' in ac.keys()):
|
if (ac["ver"] == 1) and ("nic_s" in ac.keys()):
|
||||||
ac['Rc'], ac['VPL'] = pms.adsb.nic_v1(msg, ac['nic_s'])
|
ac["Rc"], ac["VPL"] = pms.adsb.nic_v1(msg, ac["nic_s"])
|
||||||
elif (ac['ver'] == 2) and ('nic_a' in ac.keys()) and ('nic_bc' in ac.keys()):
|
elif (
|
||||||
ac['Rc'] = pms.adsb.nic_v2(msg, ac['nic_a'], ac['nic_bc'])
|
(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:
|
if tc == 19:
|
||||||
ac['HVE'], ac['VVE'] = pms.adsb.nuc_v(msg)
|
ac["HVE"], ac["VVE"] = pms.adsb.nuc_v(msg)
|
||||||
if ac['ver'] in [1, 2]:
|
if ac["ver"] in [1, 2]:
|
||||||
ac['HFOMr'], ac['VFOMr'] = pms.adsb.nac_v(msg)
|
ac["HFOMr"], ac["VFOMr"] = pms.adsb.nac_v(msg)
|
||||||
|
|
||||||
if tc == 29:
|
if tc == 29:
|
||||||
ac['PE_RCu'], ac['PE_VPL'], ac['base'] = pms.adsb.sil(msg, ac['ver'])
|
ac["PE_RCu"], ac["PE_VPL"], ac["base"] = pms.adsb.sil(
|
||||||
ac['EPU'], ac['VEPU'] = pms.adsb.nac_p(msg)
|
msg, ac["ver"]
|
||||||
|
)
|
||||||
|
ac["EPU"], ac["VEPU"] = pms.adsb.nac_p(msg)
|
||||||
|
|
||||||
if tc == 31:
|
if tc == 31:
|
||||||
ac['ver'] = pms.adsb.version(msg)
|
ac["ver"] = pms.adsb.version(msg)
|
||||||
ac['EPU'], ac['VEPU'] = pms.adsb.nac_p(msg)
|
ac["EPU"], ac["VEPU"] = pms.adsb.nac_p(msg)
|
||||||
ac['PE_RCu'], ac['PE_VPL'], ac['sil_base'] = pms.adsb.sil(msg, ac['ver'])
|
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)
|
|
||||||
|
|
||||||
|
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
|
# process commb message
|
||||||
for t, msg in zip(commb_ts, commb_msgs):
|
for t, msg in zip(commb_ts, commb_msgs):
|
||||||
@@ -183,32 +198,34 @@ class Stream():
|
|||||||
if icao not in self.acs:
|
if icao not in self.acs:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
self.acs[icao]["live"] = int(t)
|
||||||
|
|
||||||
bds = pms.bds.infer(msg)
|
bds = pms.bds.infer(msg)
|
||||||
|
|
||||||
if bds == 'BDS50':
|
if bds == "BDS50":
|
||||||
roll50 = pms.commb.roll50(msg)
|
roll50 = pms.commb.roll50(msg)
|
||||||
trk50 = pms.commb.trk50(msg)
|
trk50 = pms.commb.trk50(msg)
|
||||||
rtrk50 = pms.commb.rtrk50(msg)
|
rtrk50 = pms.commb.rtrk50(msg)
|
||||||
gs50 = pms.commb.gs50(msg)
|
gs50 = pms.commb.gs50(msg)
|
||||||
tas50 = pms.commb.tas50(msg)
|
tas50 = pms.commb.tas50(msg)
|
||||||
|
|
||||||
self.acs[icao]['t50'] = t
|
self.acs[icao]["t50"] = t
|
||||||
if tas50:
|
if tas50:
|
||||||
self.acs[icao]['tas'] = tas50
|
self.acs[icao]["tas"] = tas50
|
||||||
output_buffer.append([t, icao, 'tas50', tas50])
|
output_buffer.append([t, icao, "tas50", tas50])
|
||||||
if roll50:
|
if roll50:
|
||||||
self.acs[icao]['roll'] = roll50
|
self.acs[icao]["roll"] = roll50
|
||||||
output_buffer.append([t, icao, 'roll50', roll50])
|
output_buffer.append([t, icao, "roll50", roll50])
|
||||||
if rtrk50:
|
if rtrk50:
|
||||||
self.acs[icao]['rtrk'] = rtrk50
|
self.acs[icao]["rtrk"] = rtrk50
|
||||||
output_buffer.append([t, icao, 'rtrk50', rtrk50])
|
output_buffer.append([t, icao, "rtrk50", rtrk50])
|
||||||
|
|
||||||
if trk50:
|
if trk50:
|
||||||
output_buffer.append([t, icao, 'trk50', trk50])
|
output_buffer.append([t, icao, "trk50", trk50])
|
||||||
if gs50:
|
if gs50:
|
||||||
output_buffer.append([t, icao, 'gs50', gs50])
|
output_buffer.append([t, icao, "gs50", gs50])
|
||||||
|
|
||||||
elif bds == 'BDS60':
|
elif bds == "BDS60":
|
||||||
ias60 = pms.commb.ias60(msg)
|
ias60 = pms.commb.ias60(msg)
|
||||||
hdg60 = pms.commb.hdg60(msg)
|
hdg60 = pms.commb.hdg60(msg)
|
||||||
mach60 = pms.commb.mach60(msg)
|
mach60 = pms.commb.mach60(msg)
|
||||||
@@ -216,28 +233,28 @@ class Stream():
|
|||||||
roc60ins = pms.commb.vr60ins(msg)
|
roc60ins = pms.commb.vr60ins(msg)
|
||||||
|
|
||||||
if ias60 or hdg60 or mach60:
|
if ias60 or hdg60 or mach60:
|
||||||
self.acs[icao]['t60'] = t
|
self.acs[icao]["t60"] = t
|
||||||
if ias60:
|
if ias60:
|
||||||
self.acs[icao]['ias'] = ias60
|
self.acs[icao]["ias"] = ias60
|
||||||
if hdg60:
|
if hdg60:
|
||||||
self.acs[icao]['hdg'] = hdg60
|
self.acs[icao]["hdg"] = hdg60
|
||||||
if mach60:
|
if mach60:
|
||||||
self.acs[icao]['mach'] = mach60
|
self.acs[icao]["mach"] = mach60
|
||||||
|
|
||||||
if roc60baro:
|
if roc60baro:
|
||||||
output_buffer.append([t, icao, 'roc60baro', roc60baro])
|
output_buffer.append([t, icao, "roc60baro", roc60baro])
|
||||||
if roc60ins:
|
if roc60ins:
|
||||||
output_buffer.append([t, icao, 'roc60ins', roc60ins])
|
output_buffer.append([t, icao, "roc60ins", roc60ins])
|
||||||
|
|
||||||
# clear up old data
|
# clear up old data
|
||||||
for icao in list(self.acs.keys()):
|
for icao in list(self.acs.keys()):
|
||||||
if self.t - self.acs[icao]['live'] > self.cache_timeout:
|
if self.t - self.acs[icao]["live"] > self.cache_timeout:
|
||||||
del self.acs[icao]
|
del self.acs[icao]
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if self.dumpto is not None:
|
if self.dumpto is not None:
|
||||||
dh = str(datetime.datetime.now().strftime("%Y%m%d_%H"))
|
dh = str(datetime.datetime.now().strftime("%Y%m%d_%H"))
|
||||||
fn = self.dumpto + '/pymodes_dump_%s.csv' % dh
|
fn = self.dumpto + "/pymodes_dump_%s.csv" % dh
|
||||||
output_buffer.sort(key=lambda x: x[0])
|
output_buffer.sort(key=lambda x: x[0])
|
||||||
with open(fn, "a") as f:
|
with open(fn, "a") as f:
|
||||||
writer = csv.writer(f)
|
writer = csv.writer(f)
|
||||||
@@ -248,8 +265,4 @@ class Stream():
|
|||||||
def get_aircraft(self):
|
def get_aircraft(self):
|
||||||
"""all aircraft that are stored in memeory"""
|
"""all aircraft that are stored in memeory"""
|
||||||
acs = self.acs
|
acs = self.acs
|
||||||
icaos = list(acs.keys())
|
|
||||||
for icao in icaos:
|
|
||||||
if acs[icao]['lat'] is None:
|
|
||||||
acs.pop(icao)
|
|
||||||
return acs
|
return acs
|
||||||
|
|||||||
Reference in New Issue
Block a user