Skip to content

Commit

Permalink
Add simple stats for call signs, stored into json file
Browse files Browse the repository at this point in the history
  • Loading branch information
Wolfrax committed Mar 18, 2018
1 parent 2a126c3 commit 2db463f
Show file tree
Hide file tree
Showing 10 changed files with 164 additions and 4 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ Configuration for spots is in `spots_config.json`. Follows json syntax with no e
* log backup count (integer): How many roted log files to keep
* spots server address (localhost or ip-address): the address for the server
* spots server port (5051): the server port
* flight db name (string): name of databse file to store flight counts

## Client/Server

Expand Down
2 changes: 1 addition & 1 deletion radar/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from pkg_resources import get_distribution, DistributionNotFound
import os.path

VERSION = "2.1"
VERSION = "2.2"

try:
_dist = get_distribution('spots')
Expand Down
1 change: 1 addition & 0 deletions radar/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ class ADSB:
cfg_log_backup_count = config["log backup count"]
cfg_server_address = config["spots server address"]
cfg_server_port = config["spots server port"]
cfg_flight_db_name = config["flight db name"]

def __init__(self):
pass
Expand Down
5 changes: 5 additions & 0 deletions radar/emitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ def spots_statistics():
return app.response_class(get_msg("GET STATISTICS STR"), content_type='application/json')


@app.route("/spots/flight_db")
def spots_flight_db():
return app.response_class(get_msg("GET FLIGHT_DB STR"), content_type='application/json')


if __name__ == "__main__":
print "Will listen on {}:{}".format(cfg_server_address, cfg_server_port)
app.run(host='0.0.0.0', debug=True)
81 changes: 81 additions & 0 deletions radar/flight_db_tool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import os
import simplejson
import sys
import argparse
import pprint


class FlightDB:
"""
The FlightDB class is a simple persistent storage of flight data.
It stores data int a json file and sends the top 10 (configurable) flights to the client
"""

def __init__(self, loc):
location = os.path.expanduser(loc)
self.loc = loc
if os.path.exists(location):
self.db = simplejson.load(open(self.loc, 'rb'))
else:
print("File {} does not exists!".format(loc))
sys.exit(0)

def filter(self, limit, lte=True):
res = {}
for k, v in self.db['flights'].iteritems():
if lte:
if v <= limit:
res[k] = v
else:
if v >= limit:
res[k] = v
return res

def max_val(self):
return self.db['flights'][max(self.db['flights'], key=self.db['flights'].get)]

def get_tot_cnt(self):
return self.db['total_cnt']

def get_version(self):
return self.db['version']

def get_start_date(self):
return self.db['start_date']

def get_no_flights(self):
return len(self.db['flights'])

if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("file", help="the database (json) file to use")
args = parser.parse_args()
pp = pprint.PrettyPrinter(indent=4)

db = FlightDB(args.file)
print("Info on {}".format(args.file))
print("Version is {}, start date is {}, no of fligths are {}, total count is {}".format(db.get_version(),
db.get_start_date(),
db.get_no_flights(),
db.get_tot_cnt()))
print("============")
print

rare = db.filter(1)
print("All the rare flights are:")
pp.pprint(rare)
print("============")
print

max = db.max_val()
common = db.filter(max - 10, lte=False)
print("The most common flights are:")
pp.pprint(common)
print("============")
print

print("That's all!")



63 changes: 62 additions & 1 deletion radar/radar.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
import time
import sys
import server

import operator
import os
import simplejson

__author__ = 'Wolfrax'

Expand Down Expand Up @@ -46,6 +48,11 @@
'msg' is the decoded Squitter object
The blip dictionary is in turn read recurrently by a separate thread for display.
To collect some (rather pointless) statistics using call signs counts a smple FlightDB class exists.
Call sign statistics is stored into a json structure which is stored recurrently on file (file name is configurable).
A simple tool to dump the content of the file exists (flight_db_tool.py), A client can ask for this information using
"GET FLIGHT_DB STR" (implemented in server.py).
"""


Expand Down Expand Up @@ -139,6 +146,44 @@ def close(self):
curses.endwin()


class FlightDB:
"""
The FlightDB class is a simple persistent storage of flight data.
It stores data int a json file and sends the flights to the client
"""

def __init__(self, loc):
location = os.path.expanduser(loc)
self.loc = loc
if os.path.exists(location):
self.db = simplejson.load(open(self.loc, 'rb'))
else:
self.db = {'version': basic.ADSB.VERSION,
'start_date': basic.statistics['start_time_string'],
'total_cnt': 0,
'flights': {}}

def add(self, flight):
if flight in self.db['flights']:
self.db['flights'][flight] += 1
else:
self.db['flights'][flight] = 1
self.db['total_cnt'] += 1

def get_head(self):
return {k: v for k, v in self.db.iteritems() if k in ('version', 'start_date', 'total_cnt')}

def get_flights(self):
# Return a sorted dictionary of flights, descending order
# See https://stackoverflow.com/questions/613183/how-do-i-sort-a-dictionary-by-value
return {'flights': sorted(self.db['flights'].items(), key=operator.itemgetter(1), reverse=True)}

def dump(self):
with open(self.loc, 'wt') as f:
simplejson.dump(self.db, f, indent=4*' ')


class Radar(basic.ADSB, threading.Thread):
"""
The Radar class is where squitter messages are stored and processed.
Expand Down Expand Up @@ -167,14 +212,26 @@ def __init__(self):

self.daemon = True # This is a daemon thread
self.logger = logging.getLogger('spots.Radar')

# We create a simple persistent storage for counting of flights
self.flight_db = FlightDB(basic.ADSB.cfg_flight_db_name)
self.flight_db.dump()

self.blip_timer = basic.RepeatTimer(1, self._scan_blips, "Radar blip timer")
self.stat_timer = basic.RepeatTimer(3600, self._show_stats, "Radar stat timer")
self.blip_timer.start()
self.stat_timer.start()

def _show_stats(self):
# Save to persistent storage, flights and statistics
self.flight_db.dump()
self.logger.info(str(basic.statistics))

def get_flight_db(self):
fl = self.flight_db.get_head()
fl.update(self.flight_db.get_flights())
return fl

@staticmethod
def get_statistics():
return basic.statistics.data
Expand Down Expand Up @@ -244,6 +301,9 @@ def _blip_add(self, msg):
if not self.blips[icao]['msg'].decodeCPR_relative():
self.blips[icao]['msg'].decodeCPR()

if msg['call_sign'] != "":
self.flight_db.add(msg['call_sign'])

self.lock.release()
if self.cfg_verbose_logging:
self.logger.info("{}".format(str(msg)))
Expand Down Expand Up @@ -297,6 +357,7 @@ def run(self):
def _die(self):
self.logger.info("Radar dying")

self.flight_db.dump()
self.finished.set()
self.blip_timer.cancel()
self.stat_timer.cancel()
Expand Down
3 changes: 3 additions & 0 deletions radar/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
The Spots server implements a threaded server, one thread per request. The requests follow a simple protocol
"GET DATA STR": message from the client will return the radar blip messages in serialized/json format
"GET STATISTICS STR": message from the client will return spots statistics in serialized/json format
"GET FLIGHT_DB STR": message from the client will return spots flight database in serialized/json format
"""


Expand All @@ -23,6 +24,8 @@ def handle(self):
response = self.server.radar.get_blips_serialized()
elif cmd == "GET STATISTICS STR":
response = self.server.radar.get_statistics()
elif cmd == "GET FLIGHT_DB STR":
response = self.server.radar.get_flight_db()
else:
return

Expand Down
3 changes: 2 additions & 1 deletion radar/spots_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@
"log max bytes": 1048576,
"log backup count": 10,
"spots server address": "",
"spots server port": 5051
"spots server port": 5051,
"flight db name": "spots_flight_db.json"
}
7 changes: 7 additions & 0 deletions radar/tuner.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import threading
import rtlsdr
import time
import signal

__author__ = 'Wolfrax'

Expand Down Expand Up @@ -149,7 +150,13 @@ def die(self):

self.logger.info(str(basic.statistics))

def exit_terminate(self, signum, frame):
self.logger.info("Caught SIGTERM")
self.die()

def read(self, cb_func):
signal.signal(signal.SIGTERM, self.exit_terminate)

self._cb_func = cb_func
try:
while not self.finished.is_set():
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
data_files=[('', ['adsb-packet.png', 'spots.png', 'LICENSE.txt', 'README.md', 'README']),
('client', ['map.html', 'spots.html', 'spots.js']),
('radar', ['modes1.bin', 'radar.conf', 'spots_config.json', 'spots_emitter.conf', 'squitter.json'])],
install_requires=['docutils>=0.3', 'Flask', 'pyrtlsdr'],
install_requires=['docutils>=0.3', 'Flask', 'pyrtlsdr', 'simplejson'],
url='https://github.com/Wolfrax/spots',
license='GPL',
author='Mats Melander',
Expand Down

0 comments on commit 2db463f

Please sign in to comment.