mlat server working in the client-server direction. other way round untested.
This commit is contained in:
parent
1e2b8a4f46
commit
fd12402462
@ -21,6 +21,7 @@ include(GrPython)
|
|||||||
|
|
||||||
GR_PYTHON_INSTALL(
|
GR_PYTHON_INSTALL(
|
||||||
PROGRAMS
|
PROGRAMS
|
||||||
|
mlat_server
|
||||||
modes_rx
|
modes_rx
|
||||||
modes_gui
|
modes_gui
|
||||||
uhd_modes.py
|
uhd_modes.py
|
||||||
|
@ -20,6 +20,16 @@
|
|||||||
# Boston, MA 02110-1301, USA.
|
# Boston, MA 02110-1301, USA.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
#simple multilateration server app.
|
||||||
|
#looks at received messages and then attempts to multilaterate positions
|
||||||
|
#will not attempt clock synchronization yet; it's up to clients to present
|
||||||
|
#accurate timestamps. later on we can do clock sync based on ADS-B packets.
|
||||||
|
|
||||||
|
#SECURITY NOTE: THIS SERVER WAS WRITTEN WITH NO REGARD TOWARD SECURITY.
|
||||||
|
#NO REAL ATTEMPT AT DATA VALIDATION, MUCH LESS SECURITY, HAS BEEN MADE.
|
||||||
|
#USE THIS PROGRAM AT YOUR OWN RISK AND WITH THE UNDERSTANDING THAT THE
|
||||||
|
#INTERNET IS A SCARY PLACE FULL OF MEAN PEOPLE.
|
||||||
|
|
||||||
import time, os, sys, socket, struct
|
import time, os, sys, socket, struct
|
||||||
from string import split, join
|
from string import split, join
|
||||||
from datetime import *
|
from datetime import *
|
||||||
@ -28,29 +38,26 @@ import pickle
|
|||||||
import time
|
import time
|
||||||
import bisect
|
import bisect
|
||||||
|
|
||||||
#simple multilateration server app.
|
|
||||||
#accepts connections from clients (need a raw_client output thing)
|
|
||||||
#looks at received messages and then attempts to multilaterate positions
|
|
||||||
#will not attempt clock synchronization yet; it's up to clients to present
|
|
||||||
#accurate timestamps. later on we can do clock sync based on ADS-B packets.
|
|
||||||
|
|
||||||
|
|
||||||
#how to store records for quick retrieval?
|
|
||||||
#the data is really a hash; we use it to find correlated records.
|
|
||||||
#self._records should be a dict of replies
|
|
||||||
#so: { <adsbdata>: [{ "addr": "192.168.10.1", "secs": 11, "frac_secs": 0.123456 }, {....}...] ... }
|
|
||||||
#adsbdata should be an int.
|
|
||||||
|
|
||||||
#change this to 0 for ASCII format for debugging. use HIGHEST_PROTOCOL
|
#change this to 0 for ASCII format for debugging. use HIGHEST_PROTOCOL
|
||||||
#for actual use to keep the pickle size down.
|
#for actual use to keep the pickle size down.
|
||||||
#pickle_prot = 0
|
pickle_prot = 0
|
||||||
pickle_prot = pickle.HIGHEST_PROTOCOL
|
#pickle_prot = pickle.HIGHEST_PROTOCOL
|
||||||
|
|
||||||
class rx_data:
|
class rx_data:
|
||||||
def __init__(self):
|
secs = 0
|
||||||
self.secs = 0
|
frac_secs = 0.0
|
||||||
self.frac_secs = 0.0
|
data = None
|
||||||
self.data = None
|
|
||||||
|
class mlat_data:
|
||||||
|
data = None
|
||||||
|
secs = 0
|
||||||
|
frac_secs = 0.0
|
||||||
|
numstations = 0
|
||||||
|
lat = 0
|
||||||
|
lon = 0
|
||||||
|
alt = 0
|
||||||
|
hdop = 0.0
|
||||||
|
vdop = 0.0
|
||||||
|
|
||||||
class stamp:
|
class stamp:
|
||||||
def __init__(self, clientinfo, secs, frac_secs):
|
def __init__(self, clientinfo, secs, frac_secs):
|
||||||
@ -84,6 +91,7 @@ class client_info:
|
|||||||
self.position = []
|
self.position = []
|
||||||
self.offset_secs = 0
|
self.offset_secs = 0
|
||||||
self.offset_frac_secs = 0.0
|
self.offset_frac_secs = 0.0
|
||||||
|
self.time_source = None
|
||||||
|
|
||||||
class connection:
|
class connection:
|
||||||
def __init__(self, addr, sock, clientinfo):
|
def __init__(self, addr, sock, clientinfo):
|
||||||
@ -101,8 +109,8 @@ class mlat_server:
|
|||||||
self._conns = [] #list of active connections
|
self._conns = [] #list of active connections
|
||||||
self._reports = {} #hashed by data
|
self._reports = {} #hashed by data
|
||||||
self._lastreport = 0 #used for pruning
|
self._lastreport = 0 #used for pruning
|
||||||
|
|
||||||
self._parser = air_modes.parse(None)
|
self._parser = air_modes.parse(None)
|
||||||
|
self._remnant = None #for piecing together messages across packets
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
self._s.close()
|
self._s.close()
|
||||||
@ -111,24 +119,34 @@ class mlat_server:
|
|||||||
for conn in self._conns:
|
for conn in self._conns:
|
||||||
pkt = None
|
pkt = None
|
||||||
try:
|
try:
|
||||||
pkt = conn.sock.recv(1024)
|
pkt = conn.sock.recv(4096)
|
||||||
except socket.error:
|
except socket.error:
|
||||||
self._conns.remove(conn)
|
self._conns.remove(conn)
|
||||||
if not pkt: break
|
if not pkt: break
|
||||||
|
#print "Received message from %s with length %i" % (conn.clientinfo.name, len(pkt))
|
||||||
try:
|
try:
|
||||||
msglist = pickle.loads(pkt)
|
for msg in pkt.splitlines(True):
|
||||||
for msg in msglist:
|
if msg.endswith("\n"):
|
||||||
st = stamp(conn.clientinfo, msg.secs, msg.frac_secs)
|
if self._remnant:
|
||||||
if msg.data not in self._reports:
|
msg = self._remnant + msg
|
||||||
self._reports[msg.data] = []
|
self._remnant = None
|
||||||
|
[data, ecc, reference, timestamp_int, timestamp_frac] = msg.split()
|
||||||
|
st = stamp(conn.clientinfo, int(timestamp_int), float(timestamp_frac))
|
||||||
|
if data not in self._reports:
|
||||||
|
self._reports[data] = []
|
||||||
|
|
||||||
#ordered insert
|
#ordered insert
|
||||||
ordered_insert(self._reports[msg.data], st)
|
ordered_insert(self._reports[data], st)
|
||||||
if st.tofloat() > self._lastreport:
|
if st.tofloat() > self._lastreport:
|
||||||
self._lastreport = st.tofloat()
|
self._lastreport = st.tofloat()
|
||||||
|
else:
|
||||||
|
if self._remnant is not None:
|
||||||
|
raise Exception("Malformed data: " + msg)
|
||||||
|
else:
|
||||||
|
self._remnant = msg
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print "Invalid message from %s: %s" % (conn.addr, pkt)
|
print "Invalid message from %s: %s..." % (conn.addr, pkt[:64])
|
||||||
print e
|
print e
|
||||||
|
|
||||||
#self.prune()
|
#self.prune()
|
||||||
@ -160,9 +178,9 @@ class mlat_server:
|
|||||||
def get_eligible_reports(self):
|
def get_eligible_reports(self):
|
||||||
groups = []
|
groups = []
|
||||||
for data,stamps in self._reports.iteritems():
|
for data,stamps in self._reports.iteritems():
|
||||||
if len(stamps) > 2: #quick check before we do a set()
|
if len(stamps) >= 4: #quick check before we do a set()
|
||||||
stations = set([st.clientinfo for st in stamps])
|
stations = set([st.clientinfo for st in stamps])
|
||||||
if len(stations) > 2:
|
if len(stations) >= 4:
|
||||||
i=0
|
i=0
|
||||||
#it's O(n) since the list is already sorted
|
#it's O(n) since the list is already sorted
|
||||||
#can probably be cleaner and more concise
|
#can probably be cleaner and more concise
|
||||||
@ -178,30 +196,32 @@ class mlat_server:
|
|||||||
if st.clientinfo == cinfo:
|
if st.clientinfo == cinfo:
|
||||||
deduped.append(st)
|
deduped.append(st)
|
||||||
break
|
break
|
||||||
if len(deduped) > 2:
|
if len(deduped) >= 4:
|
||||||
groups.append({"data": data, "stamps": deduped})
|
groups.append({"data": data, "stamps": deduped})
|
||||||
|
|
||||||
|
#TODO: pop the entries so you don't issue duplicate reports
|
||||||
if len(groups) > 0:
|
if len(groups) > 0:
|
||||||
return groups
|
return groups
|
||||||
return None
|
return None
|
||||||
|
|
||||||
#issue multilaterated positions
|
#issue multilaterated positions
|
||||||
def output(self, msg):
|
def output(self, txlist):
|
||||||
#do something here to compose a message
|
#TODO: buffer this like the client does
|
||||||
if msg is not None:
|
msg = pickle.dumps(txlist, pickle_prot)
|
||||||
try:
|
if msg is not None:
|
||||||
for conn in self._conns[:]: #iterate over a copy of the list
|
try:
|
||||||
conn.sock.send(msg)
|
for conn in self._conns[:]: #iterate over a copy of the list
|
||||||
except socket.error:
|
conn.sock.send(msg)
|
||||||
print "Client %s disconnected" % conn.clientinfo.name
|
except socket.error:
|
||||||
self._conns.remove(conn)
|
print "Client %s disconnected" % conn.clientinfo.name
|
||||||
print "Connections: ", len(self._conns)
|
self._conns.remove(conn)
|
||||||
|
print "Connections: ", len(self._conns)
|
||||||
|
|
||||||
#add a new connection to the list
|
#add a new connection to the list
|
||||||
def add_pending_conns(self):
|
def add_pending_conns(self):
|
||||||
try:
|
try:
|
||||||
conn, addr = self._s.accept()
|
conn, addr = self._s.accept()
|
||||||
conn.send("HELO\n") #yeah it's like that
|
conn.send("HELO") #yeah it's like that
|
||||||
msg = conn.recv(1024)
|
msg = conn.recv(1024)
|
||||||
if not msg:
|
if not msg:
|
||||||
return
|
return
|
||||||
@ -237,9 +257,9 @@ def get_modes_altitude(data):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
if __name__=="__main__":
|
if __name__=="__main__":
|
||||||
srv = mlat_server("nothin'", 31337)
|
srv = mlat_server("nothin'", 19005)
|
||||||
while 1:
|
while 1:
|
||||||
srv.output("Buttes")
|
#srv.output("Buttes")
|
||||||
srv.get_messages()
|
srv.get_messages()
|
||||||
srv.add_pending_conns()
|
srv.add_pending_conns()
|
||||||
reps = srv.get_eligible_reports()
|
reps = srv.get_eligible_reports()
|
||||||
@ -254,6 +274,7 @@ if __name__=="__main__":
|
|||||||
#it's expecting a list of tuples [(station[], timestamp)...]
|
#it's expecting a list of tuples [(station[], timestamp)...]
|
||||||
#also have to parse the data to pull altitude out of the mix
|
#also have to parse the data to pull altitude out of the mix
|
||||||
if reps:
|
if reps:
|
||||||
|
txlist = []
|
||||||
for rep in reps:
|
for rep in reps:
|
||||||
alt = get_modes_altitude(air_modes.modes_reply(rep["data"]))
|
alt = get_modes_altitude(air_modes.modes_reply(rep["data"]))
|
||||||
if (alt is None and len(rep["stamps"]) > 3) or alt is not None:
|
if (alt is None and len(rep["stamps"]) > 3) or alt is not None:
|
||||||
@ -261,10 +282,25 @@ if __name__=="__main__":
|
|||||||
print mlat_list
|
print mlat_list
|
||||||
#multilaterate!
|
#multilaterate!
|
||||||
try:
|
try:
|
||||||
pos = air_modes.mlat.mlat(mlat_list, alt)
|
pos, senttime = air_modes.mlat.mlat(mlat_list, alt)
|
||||||
if pos is not None:
|
if pos is not None:
|
||||||
print pos
|
print "Resolved position: ", pos
|
||||||
|
#add to message stack
|
||||||
|
msg = mlat_data()
|
||||||
|
msg.data = rep["data"]
|
||||||
|
#TODO: fix loss of precision in mlat() timestamp output
|
||||||
|
msg.secs = int(senttime)
|
||||||
|
msg.frac_secs = float(senttime) - msg.secs
|
||||||
|
msg.numstations = len(mlat_list)
|
||||||
|
msg.lat = pos[0]
|
||||||
|
msg.lon = pos[1]
|
||||||
|
msg.alt = pos[2]
|
||||||
|
msg.hdop = 0.0
|
||||||
|
msg.vdop = 0.0
|
||||||
|
txlist.append(msg)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print e
|
print e
|
||||||
|
srv.output(txlist)
|
||||||
|
|
||||||
time.sleep(0.3)
|
time.sleep(0.3)
|
@ -36,13 +36,15 @@ def get_pos(time):
|
|||||||
ac_starting_pos[2]]
|
ac_starting_pos[2]]
|
||||||
|
|
||||||
def get_simulated_timestamp(time, position):
|
def get_simulated_timestamp(time, position):
|
||||||
return time + numpy.linalg.norm(numpy.array(air_modes.mlat.llh2ecef(position))-numpy.array(air_modes.mlat.llh2geoid(info.position))) / air_modes.mlat.c
|
prange = numpy.linalg.norm(numpy.array(air_modes.mlat.llh2ecef(position))-numpy.array(air_modes.mlat.llh2geoid(info.position))) / air_modes.mlat.c
|
||||||
|
return time + prange + random.random()*60e-9 - 30e-9
|
||||||
|
|
||||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
sock.setblocking(1)
|
sock.setblocking(1)
|
||||||
sock.connect(("localhost", 31337))
|
sock.connect(("localhost", 19005))
|
||||||
sock.send(pickle.dumps(info))
|
sock.send(pickle.dumps(info))
|
||||||
print sock.recv(1024)
|
print sock.recv(1024)
|
||||||
|
sock.setblocking(0)
|
||||||
ts = 0.0
|
ts = 0.0
|
||||||
while 1:
|
while 1:
|
||||||
pos = get_pos(ts)
|
pos = get_pos(ts)
|
||||||
@ -53,6 +55,11 @@ while 1:
|
|||||||
data1.frac_secs = float(stamp)
|
data1.frac_secs = float(stamp)
|
||||||
data1.frac_secs -= int(data1.frac_secs)
|
data1.frac_secs -= int(data1.frac_secs)
|
||||||
sock.send(pickle.dumps([data1]))
|
sock.send(pickle.dumps([data1]))
|
||||||
|
try:
|
||||||
|
positions = sock.recv(1024)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if positions: print positions
|
||||||
ts+=1
|
ts+=1
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
|
@ -51,17 +51,22 @@ class adsb_rx_block (gr.top_block):
|
|||||||
self.args = args
|
self.args = args
|
||||||
rate = int(options.rate)
|
rate = int(options.rate)
|
||||||
use_resampler = False
|
use_resampler = False
|
||||||
|
self.time_source = None
|
||||||
|
|
||||||
if options.filename is None and options.udp is None and not options.rtlsdr:
|
if options.filename is None and options.udp is None and not options.rtlsdr:
|
||||||
#UHD source by default
|
#UHD source by default
|
||||||
from gnuradio import uhd
|
from gnuradio import uhd
|
||||||
self.u = uhd.single_usrp_source(options.args, uhd.io_type_t.COMPLEX_FLOAT32, 1)
|
self.u = uhd.single_usrp_source(options.args, uhd.io_type_t.COMPLEX_FLOAT32, 1)
|
||||||
time_spec = uhd.time_spec(0.0)
|
|
||||||
self.u.set_time_now(time_spec)
|
|
||||||
|
|
||||||
#if(options.rx_subdev_spec is None):
|
#check for GPSDO
|
||||||
# options.rx_subdev_spec = ""
|
#if you have a GPSDO, UHD will automatically set the timestamp to UTC time
|
||||||
#self.u.set_subdev_spec(options.rx_subdev_spec)
|
#as well as automatically set the clock to lock to GPSDO.
|
||||||
|
if self.u.get_time_source(0) == 'gpsdo':
|
||||||
|
self.time_source = 'gpsdo'
|
||||||
|
else:
|
||||||
|
self.time_source = None
|
||||||
|
self.u.set_time_now(uhd.time_spec(0.0))
|
||||||
|
|
||||||
if not options.antenna is None:
|
if not options.antenna is None:
|
||||||
self.u.set_antenna(options.antenna)
|
self.u.set_antenna(options.antenna)
|
||||||
|
|
||||||
@ -126,6 +131,11 @@ class adsb_rx_block (gr.top_block):
|
|||||||
def printraw(msg):
|
def printraw(msg):
|
||||||
print msg
|
print msg
|
||||||
|
|
||||||
|
def printmlat(msg):
|
||||||
|
msglist = pickle.loads(msg)
|
||||||
|
for msg in msglist:
|
||||||
|
print "mlat message: %x at [%f, %f]" % (msg.data, msg.lat, msg.lon)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
usage = "%prog: [options] output filename"
|
usage = "%prog: [options] output filename"
|
||||||
parser = OptionParser(option_class=eng_option, usage=usage)
|
parser = OptionParser(option_class=eng_option, usage=usage)
|
||||||
@ -165,6 +175,8 @@ if __name__ == '__main__':
|
|||||||
help="Use RTLSDR dongle instead of UHD source")
|
help="Use RTLSDR dongle instead of UHD source")
|
||||||
parser.add_option("-p","--pmf", action="store_true", default=False,
|
parser.add_option("-p","--pmf", action="store_true", default=False,
|
||||||
help="Use pulse matched filtering")
|
help="Use pulse matched filtering")
|
||||||
|
parser.add_option("-s","--mlat-server", type="string", default=None,
|
||||||
|
help="Use a multilateration server to track non-ADS-B aircraft")
|
||||||
|
|
||||||
(options, args) = parser.parse_args()
|
(options, args) = parser.parse_args()
|
||||||
|
|
||||||
@ -173,8 +185,10 @@ if __name__ == '__main__':
|
|||||||
my_position = reader.next()
|
my_position = reader.next()
|
||||||
|
|
||||||
queue = gr.msg_queue()
|
queue = gr.msg_queue()
|
||||||
|
mlat_queue = None
|
||||||
|
|
||||||
outputs = [] #registry of plugin output functions
|
outputs = [] #registry of plugin output functions
|
||||||
|
mlat_outputs = [] #registry of plugin mlat handling functions
|
||||||
updates = [] #registry of plugin update functions
|
updates = [] #registry of plugin update functions
|
||||||
|
|
||||||
if options.raw is True:
|
if options.raw is True:
|
||||||
@ -205,13 +219,21 @@ if __name__ == '__main__':
|
|||||||
outputs.append(fgout.output)
|
outputs.append(fgout.output)
|
||||||
|
|
||||||
fg = adsb_rx_block(options, args, queue)
|
fg = adsb_rx_block(options, args, queue)
|
||||||
|
|
||||||
|
if options.mlat_server is not None:
|
||||||
|
mlat_queue = gr.msg_queue()
|
||||||
|
mlat_client = air_modes.mlat_client(mlat_queue, my_position, options.mlat_server, fg.time_source)
|
||||||
|
outputs.append(mlat_client.output) #output to server when we get a report
|
||||||
|
updates.append(mlat_client.get_mlat_positions) #check for replies from the server
|
||||||
|
#note: this means that get_mlat_positions and printmlat execute in the same thread.
|
||||||
|
#you could just have mlat_client spawn a thread to check its socket. might make more sense to do this.
|
||||||
|
mlat_outputs.append(printmlat)
|
||||||
|
|
||||||
runner = top_block_runner(fg)
|
runner = top_block_runner(fg)
|
||||||
|
|
||||||
while 1:
|
while 1:
|
||||||
try:
|
try:
|
||||||
#the update registry is really for the SBS1 and raw server plugins -- we're looking for new TCP connections.
|
#handle the once-per-loop updates (check for mlat responses, add TCP conns to SBS-1 plugin, etc.)
|
||||||
#i think we have to do this here rather than in the output handler because otherwise connections will stack up
|
|
||||||
#until the next output arrives
|
|
||||||
for update in updates:
|
for update in updates:
|
||||||
update()
|
update()
|
||||||
|
|
||||||
|
@ -174,8 +174,7 @@ int air_modes_slicer::work(int noutput_items,
|
|||||||
unsigned long crc = modes_check_crc(d_data, packet_length);
|
unsigned long crc = modes_check_crc(d_data, packet_length);
|
||||||
|
|
||||||
//crc for packets that aren't type 11 or type 17 is encoded with the transponder ID, which we don't know
|
//crc for packets that aren't type 11 or type 17 is encoded with the transponder ID, which we don't know
|
||||||
//therefore we toss 'em if there's syndrome
|
//we forward them if they have no low-confidence bits (see above), but this still lets some crap through.
|
||||||
//crc for the other short packets is usually nonzero, so they can't really be trusted that far
|
|
||||||
if(crc && (message_type == 11 || message_type == 17)) {continue;}
|
if(crc && (message_type == 11 || message_type == 17)) {continue;}
|
||||||
std::ostringstream payload;
|
std::ostringstream payload;
|
||||||
for(int m = 0; m < packet_length/8; m++) {
|
for(int m = 0; m < packet_length/8; m++) {
|
||||||
|
@ -35,6 +35,7 @@ GR_PYTHON_INSTALL(
|
|||||||
az_map.py
|
az_map.py
|
||||||
cpr.py
|
cpr.py
|
||||||
mlat.py
|
mlat.py
|
||||||
|
mlat_client.py
|
||||||
exceptions.py
|
exceptions.py
|
||||||
flightgear.py
|
flightgear.py
|
||||||
gui_model.py
|
gui_model.py
|
||||||
|
@ -58,6 +58,7 @@ from sql import output_sql
|
|||||||
from sbs1 import output_sbs1
|
from sbs1 import output_sbs1
|
||||||
from kml import output_kml
|
from kml import output_kml
|
||||||
from raw_server import raw_server
|
from raw_server import raw_server
|
||||||
|
from mlat_client import mlat_client
|
||||||
from exceptions import *
|
from exceptions import *
|
||||||
from az_map import *
|
from az_map import *
|
||||||
#this is try/excepted in case the user doesn't have numpy installed
|
#this is try/excepted in case the user doesn't have numpy installed
|
||||||
|
Loading…
Reference in New Issue
Block a user