adding support for rtl-sdr tunner

This commit is contained in:
Junzi Sun
2019-08-22 10:45:12 +02:00
parent 7267859548
commit 4a3c9438f7
5 changed files with 464 additions and 225 deletions
+140
View 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()
+55 -52
View File
@@ -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,36 +9,31 @@ 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, rawtype):
Thread.__init__(self) Thread.__init__(self)
self.host = host self.host = host
self.port = port self.port = port
self.buffer = [] self.buffer = []
self.socket = None
self.rawtype = rawtype 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") print("rawtype must be either avr, 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_avr_buffer(self): def read_avr_buffer(self):
# -- testing -- # -- testing --
@@ -57,9 +52,9 @@ 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 +62,7 @@ class BaseClient(Thread):
return messages return messages
def read_beast_buffer(self): def read_beast_buffer(self):
''' """
<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,7 +76,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 = []
@@ -91,16 +86,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 +107,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 +126,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 +210,33 @@ 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 = ((tsbin[0] & 0x7F) << 10) | (tsbin[1] << 2) | (tsbin[2] >> 6)
nano = ( (tsbin[2] & 0x3f) << 24) | (tsbin[3] << 16) | (tsbin[4] << 8) | tsbin[5] nano = (
ts = sec + nano*1.0e-9 ((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 +247,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 +264,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.rawtype == "beast":
messages = self.read_beast_buffer() messages = self.read_beast_buffer()
elif self.rawtype == 'avr': elif self.rawtype == "avr":
messages = self.read_avr_buffer() messages = self.read_avr_buffer()
elif self.rawtype == 'skysense': elif self.rawtype == "skysense":
messages = self.read_skysense_buffer() messages = self.read_skysense_buffer()
if not messages: if not messages:
@@ -279,8 +282,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,7 +295,7 @@ 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])
+100 -19
View File
@@ -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
@@ -19,29 +20,67 @@ COMMB_MSG = []
COMMB_TS = [] COMMB_TS = []
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", help="rtlsdr or tcp", required=True, default="tcp"
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("--server", help="server address or IP", default=None)
parser.add_argument('--show-uncertainty', dest='uncertainty', help='display uncertaint values, default off', action='store_true', required=False, default=False) parser.add_argument("--port", help="raw data port", default=None)
parser.add_argument('--dumpto', help='folder to dump decoded output', required=False, 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() args = parser.parse_args()
SOURCE = args.source
SERVER = args.server SERVER = args.server
PORT = int(args.port) PORT = args.port
RAWTYPE = args.rawtype RAWTYPE = args.rawtype
LAT0 = float(args.latlon[0]) LAT0 = float(args.latlon[0])
LON0 = float(args.latlon[1]) LON0 = float(args.latlon[1])
UNCERTAINTY = args.uncertainty UNCERTAINTY = args.uncertainty
DUMPTO = args.dumpto 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: 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 +95,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 +109,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,12 +117,55 @@ class ModesClient(BaseClient):
LOCK.release() LOCK.release()
# redirect all stdout to null, avoiding messing up with the screen class ModesRtlReader(RtlReader):
sys.stdout = open(os.devnull, 'w') """docstring for ModesRtlReader."""
client = ModesClient(host=SERVER, port=PORT, rawtype=RAWTYPE) def __init__(self):
client.daemon = True super(ModesRtlReader, self).__init__()
client.start()
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) stream = Stream(lat0=LAT0, lon0=LON0, dumpto=DUMPTO)
@@ -94,7 +175,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 +188,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
View File
@@ -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
+114 -105
View File
@@ -5,7 +5,8 @@ import datetime
import csv import csv
import pyModeS as pms import pyModeS as pms
class Stream():
class Stream:
def __init__(self, lat0, lon0, dumpto=None): def __init__(self, lat0, lon0, dumpto=None):
self.acs = dict() self.acs = dict()
@@ -14,15 +15,13 @@ class Stream():
self.lon0 = lon0 self.lon0 = lon0
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 +41,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 +85,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 +133,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 +150,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 +194,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 +229,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 +261,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