adding support for rtl-sdr tunner
This commit is contained in:
parent
7267859548
commit
4a3c9438f7
140
pyModeS/extra/rtlreader.py
Normal file
140
pyModeS/extra/rtlreader.py
Normal file
@ -0,0 +1,140 @@
|
||||
import sys
|
||||
import numpy as np
|
||||
import pyModeS as pms
|
||||
from rtlsdr import RtlSdr
|
||||
from threading import Thread
|
||||
import time
|
||||
|
||||
amplitude_threshold = 0.2
|
||||
modes_sample_rate = 2e6
|
||||
modes_frequency = 1090e6
|
||||
buffer_size = 1024 * 100
|
||||
read_size = 1024 * 8
|
||||
pbits = 8
|
||||
fbits = 112
|
||||
preamble = "1010000101000000"
|
||||
|
||||
|
||||
class RtlReader(Thread):
|
||||
def __init__(self, debug=False):
|
||||
super(RtlReader, self).__init__()
|
||||
self.signal_buffer = np.array([])
|
||||
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 = []
|
||||
|
||||
pulses_array = np.where(self.signal_buffer < amplitude_threshold, 0, 1)
|
||||
pulses = "".join(str(x) for x in pulses_array)
|
||||
|
||||
i = 0
|
||||
while i < len(pulses):
|
||||
if pulses[i] == 0:
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if pulses[i : i + pbits * 2] == preamble:
|
||||
frame_start = i + pbits * 2
|
||||
frame_end = i + pbits * 2 + (fbits + 1) * 2
|
||||
frame_pulses = pulses[frame_start:frame_end]
|
||||
|
||||
msgbin = ""
|
||||
for j in range(0, len(frame_pulses), 2):
|
||||
p2 = frame_pulses[j : j + 2]
|
||||
if p2 == "10":
|
||||
c = "1"
|
||||
elif p2 == "01":
|
||||
c = "0"
|
||||
elif p2 == "11":
|
||||
a2 = self.signal_buffer[
|
||||
frame_start + j : frame_start + j + 2
|
||||
]
|
||||
if a2[0] > a2[1]:
|
||||
c = "1"
|
||||
else:
|
||||
c = "0"
|
||||
elif p2 == "00":
|
||||
break
|
||||
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 > len(self.signal_buffer) - pbits * 2 - fbits * 2:
|
||||
break
|
||||
else:
|
||||
i += 1
|
||||
|
||||
# keep reminder of buffer for next iteration
|
||||
self.signal_buffer = self.signal_buffer[i:]
|
||||
return messages
|
||||
|
||||
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):
|
||||
self.signal_buffer = np.concatenate(
|
||||
(self.signal_buffer, np.absolute(data))
|
||||
)
|
||||
|
||||
if len(self.signal_buffer) >= buffer_size:
|
||||
try:
|
||||
messages = self._process_buffer()
|
||||
self.handle_messages(messages)
|
||||
except KeyboardInterrupt:
|
||||
sys.exit(1)
|
||||
|
||||
def handle_messages(self, messages):
|
||||
"""re-implement this method to handle the messages"""
|
||||
for msg, t in messages:
|
||||
pass
|
||||
# print("%15.9f %s" % (t, msg))
|
||||
|
||||
def run(self):
|
||||
self.sdr.read_samples_async(self._read_callback, read_size)
|
||||
# while True:
|
||||
# data = self.sdr.read_samples(read_size)
|
||||
# self._read_callback(data, None)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
rtl = RtlReader()
|
||||
rtl.debug = True
|
||||
# rtl.daemon = True
|
||||
rtl.start()
|
@ -1,6 +1,6 @@
|
||||
'''
|
||||
"""
|
||||
Stream beast raw data from a TCP server, convert to mode-s messages
|
||||
'''
|
||||
"""
|
||||
from __future__ import print_function, division
|
||||
import os
|
||||
import sys
|
||||
@ -9,36 +9,31 @@ 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)
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.buffer = []
|
||||
self.socket = None
|
||||
self.rawtype = rawtype
|
||||
if self.rawtype not in ['avr', 'beast', 'skysense']:
|
||||
if self.rawtype not in ["avr", "beast", "skysense"]:
|
||||
print("rawtype must be either avr, beast or skysense")
|
||||
os._exit(1)
|
||||
|
||||
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, 2000)
|
||||
self.socket.connect("tcp://%s:%s" % (self.host, self.port))
|
||||
|
||||
def read_avr_buffer(self):
|
||||
# -- testing --
|
||||
@ -57,9 +52,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 +62,7 @@ class BaseClient(Thread):
|
||||
return messages
|
||||
|
||||
def read_beast_buffer(self):
|
||||
'''
|
||||
"""
|
||||
<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,7 +76,7 @@ class BaseClient(Thread):
|
||||
|
||||
timestamp:
|
||||
wiki.modesbeast.com/Radarcape:Firmware_Versions#The_GPS_timestamp
|
||||
'''
|
||||
"""
|
||||
|
||||
messages_mlat = []
|
||||
msg = []
|
||||
@ -91,16 +86,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 +107,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,10 +126,10 @@ 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
|
||||
@ -215,25 +210,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
|
||||
@ -244,11 +247,11 @@ class BaseClient(Thread):
|
||||
print("%15.9f %s" % (t, msg))
|
||||
|
||||
def run(self):
|
||||
sock = self.connect()
|
||||
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 +264,11 @@ class BaseClient(Thread):
|
||||
# continue
|
||||
# -- Removed!! Cause delay in low data rate scenario --
|
||||
|
||||
if self.rawtype == 'beast':
|
||||
if self.rawtype == "beast":
|
||||
messages = self.read_beast_buffer()
|
||||
elif self.rawtype == 'avr':
|
||||
elif self.rawtype == "avr":
|
||||
messages = self.read_avr_buffer()
|
||||
elif self.rawtype == 'skysense':
|
||||
elif self.rawtype == "skysense":
|
||||
messages = self.read_skysense_buffer()
|
||||
|
||||
if not messages:
|
||||
@ -279,8 +282,8 @@ class BaseClient(Thread):
|
||||
# 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:
|
||||
@ -292,7 +295,7 @@ class BaseClient(Thread):
|
||||
print("Unexpected Error:", e)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
# for testing purpose only
|
||||
host = sys.argv[1]
|
||||
port = int(sys.argv[2])
|
||||
|
@ -9,6 +9,7 @@ import curses
|
||||
from threading import Lock
|
||||
import pyModeS as pms
|
||||
from pyModeS.extra.tcpclient import BaseClient
|
||||
from pyModeS.extra.rtlreader import RtlReader
|
||||
from pyModeS.streamer.stream import Stream
|
||||
from pyModeS.streamer.screen import Screen
|
||||
|
||||
@ -19,29 +20,67 @@ 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)
|
||||
parser.add_argument(
|
||||
"--source", help="rtlsdr or tcp", required=True, default="tcp"
|
||||
)
|
||||
parser.add_argument("--server", help="server address or IP", default=None)
|
||||
parser.add_argument("--port", help="raw data port", default=None)
|
||||
parser.add_argument("--rawtype", help="beast, avr or skysense", default=None)
|
||||
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()
|
||||
|
||||
SOURCE = args.source
|
||||
SERVER = args.server
|
||||
PORT = int(args.port)
|
||||
PORT = args.port
|
||||
RAWTYPE = args.rawtype
|
||||
LAT0 = float(args.latlon[0])
|
||||
LON0 = float(args.latlon[1])
|
||||
UNCERTAINTY = args.uncertainty
|
||||
DUMPTO = args.dumpto
|
||||
|
||||
if SOURCE == "rtlsdr":
|
||||
pass
|
||||
elif SOURCE == "tcp":
|
||||
if SERVER is None:
|
||||
print("You must specify the server for TCP source.")
|
||||
sys.exit(1)
|
||||
if PORT is None:
|
||||
print("You must specify the port for TCP source.")
|
||||
sys.exit(1)
|
||||
if RAWTYPE is None:
|
||||
print("You must specify the rawtype for TCP source.")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("Source must be rtlsdr or tcp.")
|
||||
sys.exit(1)
|
||||
|
||||
if DUMPTO is not None:
|
||||
# append to current folder except root is given
|
||||
if DUMPTO[0] != '/':
|
||||
DUMPTO = os.getcwd() + '/' + DUMPTO
|
||||
if DUMPTO[0] != "/":
|
||||
DUMPTO = os.getcwd() + "/" + 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)
|
||||
|
||||
|
||||
@ -56,7 +95,7 @@ class ModesClient(BaseClient):
|
||||
local_buffer_ehs_ts = []
|
||||
|
||||
for msg, t in messages:
|
||||
if len(msg) < 28: # only process long messages
|
||||
if len(msg) < 28: # only process long messages
|
||||
continue
|
||||
|
||||
df = pms.df(msg)
|
||||
@ -70,7 +109,6 @@ class ModesClient(BaseClient):
|
||||
else:
|
||||
continue
|
||||
|
||||
|
||||
LOCK.acquire()
|
||||
ADSB_MSG.extend(local_buffer_adsb_msg)
|
||||
ADSB_TS.extend(local_buffer_adsb_ts)
|
||||
@ -79,12 +117,55 @@ class ModesClient(BaseClient):
|
||||
LOCK.release()
|
||||
|
||||
|
||||
# redirect all stdout to null, avoiding messing up with the screen
|
||||
sys.stdout = open(os.devnull, 'w')
|
||||
class ModesRtlReader(RtlReader):
|
||||
"""docstring for ModesRtlReader."""
|
||||
|
||||
client = ModesClient(host=SERVER, port=PORT, rawtype=RAWTYPE)
|
||||
client.daemon = True
|
||||
client.start()
|
||||
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
|
||||
sys.stdout = open(os.devnull, "w")
|
||||
|
||||
if SOURCE == "tcp":
|
||||
client = ModesClient(host=SERVER, port=PORT, rawtype=RAWTYPE)
|
||||
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)
|
||||
|
||||
@ -94,7 +175,7 @@ try:
|
||||
screen.start()
|
||||
|
||||
while True:
|
||||
if len(ADSB_MSG) > 200:
|
||||
if len(ADSB_MSG) > 1:
|
||||
LOCK.acquire()
|
||||
stream.process_raw(ADSB_TS, ADSB_MSG, COMMB_TS, COMMB_MSG)
|
||||
ADSB_MSG = []
|
||||
@ -107,7 +188,7 @@ try:
|
||||
try:
|
||||
screen.update_data(acs)
|
||||
screen.update()
|
||||
time.sleep(0.02)
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
|
@ -6,38 +6,39 @@ import time
|
||||
from threading import Thread
|
||||
|
||||
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):
|
||||
def __init__(self, uncertainty=False):
|
||||
Thread.__init__(self)
|
||||
@ -55,7 +56,6 @@ class Screen(Thread):
|
||||
if uncertainty:
|
||||
self.columns.extend(UNCERTAINTY_COLUMNS)
|
||||
|
||||
|
||||
def reset_cursor_pos(self):
|
||||
self.screen.move(self.y, self.x)
|
||||
|
||||
@ -64,7 +64,12 @@ class Screen(Thread):
|
||||
|
||||
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 +86,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 +109,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 +120,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,11 +144,13 @@ 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()
|
||||
|
||||
@ -168,7 +174,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
|
||||
|
@ -5,7 +5,8 @@ import datetime
|
||||
import csv
|
||||
import pyModeS as pms
|
||||
|
||||
class Stream():
|
||||
|
||||
class Stream:
|
||||
def __init__(self, lat0, lon0, dumpto=None):
|
||||
|
||||
self.acs = dict()
|
||||
@ -14,15 +15,13 @@ class Stream():
|
||||
self.lon0 = lon0
|
||||
|
||||
self.t = 0
|
||||
self.cache_timeout = 60 # seconds
|
||||
|
||||
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.
|
||||
@ -42,43 +41,43 @@ class Stream():
|
||||
|
||||
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,
|
||||
"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)
|
||||
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])
|
||||
self.acs[icao]["call"] = cs
|
||||
output_buffer.append([t, icao, "cs", cs])
|
||||
|
||||
if (5 <= tc <= 8) or (tc == 19):
|
||||
vdata = pms.adsb.velocity(msg)
|
||||
@ -86,42 +85,47 @@ class Stream():
|
||||
continue
|
||||
|
||||
spd, trk, roc, tag = vdata
|
||||
if tag != 'GS':
|
||||
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
|
||||
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])
|
||||
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):
|
||||
if 5 <= tc <= 18:
|
||||
oe = pms.adsb.oe_flag(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
|
||||
rlat = self.acs[icao]['lat']
|
||||
rlon = self.acs[icao]['lon']
|
||||
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):
|
||||
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
|
||||
)
|
||||
self.acs[icao]["t0"],
|
||||
self.acs[icao]["t1"],
|
||||
self.lat0,
|
||||
self.lon0,
|
||||
)
|
||||
except:
|
||||
# mix of surface and airborne position message
|
||||
continue
|
||||
@ -129,16 +133,16 @@ class Stream():
|
||||
latlon = None
|
||||
|
||||
if latlon is not None:
|
||||
self.acs[icao]['tpos'] = t
|
||||
self.acs[icao]['lat'] = latlon[0]
|
||||
self.acs[icao]['lon'] = latlon[1]
|
||||
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
|
||||
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])
|
||||
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)
|
||||
|
||||
@ -146,35 +150,42 @@ class Stream():
|
||||
ac = self.acs[icao]
|
||||
|
||||
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):
|
||||
ac['HPL'], ac['RCu'], ac['RCv'] = pms.adsb.nuc_p(msg)
|
||||
ac["HPL"], ac["RCu"], ac["RCv"] = pms.adsb.nuc_p(msg)
|
||||
|
||||
if (ac['ver'] == 1) and ('nic_s' in ac.keys()):
|
||||
ac['Rc'], ac['VPL'] = pms.adsb.nic_v1(msg, ac['nic_s'])
|
||||
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 (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)
|
||||
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)
|
||||
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)
|
||||
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):
|
||||
@ -183,32 +194,34 @@ class Stream():
|
||||
if icao not in self.acs:
|
||||
continue
|
||||
|
||||
self.acs[icao]["live"] = int(t)
|
||||
|
||||
bds = pms.bds.infer(msg)
|
||||
|
||||
if bds == 'BDS50':
|
||||
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
|
||||
self.acs[icao]["t50"] = t
|
||||
if tas50:
|
||||
self.acs[icao]['tas'] = tas50
|
||||
output_buffer.append([t, icao, 'tas50', 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])
|
||||
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])
|
||||
self.acs[icao]["rtrk"] = rtrk50
|
||||
output_buffer.append([t, icao, "rtrk50", rtrk50])
|
||||
|
||||
if trk50:
|
||||
output_buffer.append([t, icao, 'trk50', trk50])
|
||||
output_buffer.append([t, icao, "trk50", trk50])
|
||||
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)
|
||||
hdg60 = pms.commb.hdg60(msg)
|
||||
mach60 = pms.commb.mach60(msg)
|
||||
@ -216,28 +229,28 @@ class Stream():
|
||||
roc60ins = pms.commb.vr60ins(msg)
|
||||
|
||||
if ias60 or hdg60 or mach60:
|
||||
self.acs[icao]['t60'] = t
|
||||
self.acs[icao]["t60"] = t
|
||||
if ias60:
|
||||
self.acs[icao]['ias'] = ias60
|
||||
self.acs[icao]["ias"] = ias60
|
||||
if hdg60:
|
||||
self.acs[icao]['hdg'] = hdg60
|
||||
self.acs[icao]["hdg"] = hdg60
|
||||
if mach60:
|
||||
self.acs[icao]['mach'] = mach60
|
||||
self.acs[icao]["mach"] = mach60
|
||||
|
||||
if roc60baro:
|
||||
output_buffer.append([t, icao, 'roc60baro', roc60baro])
|
||||
output_buffer.append([t, icao, "roc60baro", roc60baro])
|
||||
if roc60ins:
|
||||
output_buffer.append([t, icao, 'roc60ins', 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:
|
||||
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
|
||||
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)
|
||||
@ -248,8 +261,4 @@ class Stream():
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user