diff --git a/README.md b/README.md index 34c1875..a9a440c 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,18 @@ Configuration for spots is in `spots_config.json`. Follows json syntax with no e * 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 database file to store flight counts, if the value is "" this function is skipped +* statistics file name (string): name of file to store statistics, which are read during start +* config file (string): name of file where personal email configuration data is stored, if the value is "" no emailing is done +* email recipient (string): name of email recipient, used when rare errors occur for notification. SMTP information is stored in the config file (above) + +config file for SMTP info use the following syntax: +{ + "//": "CAUTION: Stored in plain text!", + "SMTP_server": "smtp.gmail.com", + "SMTP_port": 587, + "GMAIL_username": "mats.melander@gmail.com", + "GMAIL_pw": "YourSercretPassword" +} ## Client/Server diff --git a/client/spots.html b/client/spots.html index c336143..466cd15 100644 --- a/client/spots.html +++ b/client/spots.html @@ -80,7 +80,41 @@

- + +
+
+

Flights

+

+
+
+ +
+
+
+
+ + + + + + + + + + + +
Top 10 FlightsCountBottom 10 FlightsCount
+
+
+
+
+ +
+
+ +
+ + diff --git a/radar/basic.py b/radar/basic.py index 12ceb76..0fe1c9c 100644 --- a/radar/basic.py +++ b/radar/basic.py @@ -5,6 +5,7 @@ import os import simplejson import logging +import shutil import __init__ as init @@ -200,6 +201,7 @@ class ADSB: cfg_config_file = config["config file"] cfg_use_email = True if cfg_config_file != "" else False cfg_email_recipient = config["email recipient"] + cfg_stats_filename = config["statistics filename"] def __init__(self): pass @@ -640,6 +642,8 @@ class Stats: data = {'spots_version': "", 'start_time': 0, 'start_time_string': "", + 'latest_start_time': 0, + 'latest_start_time_string': "", 'valid_preambles': 0, 'valid_crc': 0, 'not_valid_crc': 0, @@ -677,16 +681,44 @@ class Stats: 'df_31': 0, 'df_total': 0, 'no_unique_icao': 0, - 'flights': 0 + 'flights': 0, + 'max_lat': -90, + 'min_lat': 90, + 'max_lon': -180, + 'min_lon': 180 } icao_list = [] flight_list = {} def __init__(self): - self['spots_version'] = ADSB.VERSION - self['start_time'] = time.time() - self['start_time_string'] = time.ctime(self['start_time']) - pass + self.logger = logging.getLogger('spots.Stats') + + location = os.path.expanduser(ADSB.cfg_stats_filename) + self.loc = ADSB.cfg_stats_filename + self.loc_bck = self.loc + ".1" + + if os.path.exists(location): + try: + self.data = simplejson.load(open(self.loc, 'rb')) + + self['spots_version'] = ADSB.VERSION + self['latest_start_time'] = time.time() + self['latest_start_time_string'] = time.ctime(self['latest_start_time']) + + if self['start_time'] == 0: + self['start_time'] = self['latest_start_time'] + if self['start_time_string'] == "": + self['start_time_string'] = self['latest_start_time_string'] + except simplejson.JSONDecodeError: + try: + self.logger.info("Init, stats file corrupt, using backup") + # Current file is corrupt, try to fallback to backup file + self.db = simplejson.load(open(self.loc_bck, 'rb')) + except simplejson.JSONDecodeError: + # No joy, give up and use default values + self.logger.info("Init, DB file and backup corrupt, using defaults") + self['start_time'] = self['latest_start_time'] + self['start_time_string'] = self['latest_start_time_string'] def __setitem__(self, key, value): self.data[key] = value @@ -706,6 +738,13 @@ def add_flight(self, call_sign): self.flight_list[call_sign] = 0 self['flights'] = len(self.flight_list) + def dump(self): + if os.path.isfile(self.loc): + shutil.copy2(self.loc, self.loc_bck) # Make a backup + + with open(self.loc, 'wt') as stat_file: + simplejson.dump(self.data, stat_file, skipkeys=True, indent=4*' ') + def __str__(self): st = "\n" st += "Preambles:{}\n".format(self['valid_preambles']) @@ -746,6 +785,9 @@ def __str__(self): st += "DF30: {} ".format(self['df_30']) st += "DF31: {} ".format(self['df_31']) st += "\n" + st += "Max lat: {} Min lat: {}".format(self['max_lat'], self['min_lat']) + st += "Max lon: {} Min lat: {}".format(self['max_lon'], self['min_lon']) + st += "\n" st += "DF Total: {} ".format(self['df_total']) st += "\n" st += "No of unique icao: {} ".format(self['no_unique_icao']) diff --git a/radar/radar.py b/radar/radar.py index a114506..5a7ef43 100644 --- a/radar/radar.py +++ b/radar/radar.py @@ -261,7 +261,6 @@ def __init__(self): def _dump_flight_db(self): if basic.ADSB.cfg_use_flight_db: # Save to persistent storage, flights and statistics - self.logger.info("Dumping DB to file") self.flight_db.dump() def _show_stats(self): @@ -342,8 +341,16 @@ def _blip_add(self, msg): else: self.blips[icao] = {'msg': msg, 'timestamp': time.time(), 'count': 1} - if not self.blips[icao]['msg'].decodeCPR_relative(): - self.blips[icao]['msg'].decodeCPR() + # if not self.blips[icao]['msg'].decodeCPR_relative(): + # self.blips[icao]['msg'].decodeCPR() + + self.blips[icao]['msg'].decodeCPR() + + # Logic, decodeCPR returns True if + # 1. It successfully decodes one pair of odd+even position messages, not separated by more than 10 seconds + # 2. Give that condition 1) above is fulfilled + #if self.blips[icao]['msg'].decodeCPR(): + # self.blips[icao]['msg'].decodeCPR_relative() if basic.ADSB.cfg_use_flight_db and msg['call_sign'] != "": self.flight_db.add(msg['call_sign']) @@ -409,6 +416,7 @@ def _die(self): self.stat_timer.cancel() if self.cfg_use_text_display: self.screen.close() + basic.statistics.dump() def tuner_read(self, msgs, stop=False): """ diff --git a/radar/spots_config.json b/radar/spots_config.json index c55f702..a3dab9a 100644 --- a/radar/spots_config.json +++ b/radar/spots_config.json @@ -17,6 +17,7 @@ "spots server address": "", "spots server port": 5051, "flight db name": "spots_flight_db.json", + "statistics filename": "spots_stats.json", "config file": "/usr/etc/mm.json", "email recipient": "mats.melander@gmail.com" } \ No newline at end of file diff --git a/radar/squitter.py b/radar/squitter.py index 9f83001..b030c7a 100644 --- a/radar/squitter.py +++ b/radar/squitter.py @@ -306,8 +306,8 @@ def decodeCPR_relative(self): lat_cpr = self.odd_raw_latitude / self.MAX_17_BITS lon_cpr = self.odd_raw_longitude / self.MAX_17_BITS - j = math.floor(self.cfg_latitude / d_lat) + \ - math.floor((self.cfg_latitude % d_lat) / d_lat - lat_cpr + 0.5) # latitude index + j = int(math.floor(self.cfg_latitude / d_lat)) + \ + int(math.floor((self.cfg_latitude % d_lat) / d_lat - lat_cpr + 0.5)) # latitude index latitude = d_lat * (j + lat_cpr) @@ -316,13 +316,19 @@ def decodeCPR_relative(self): else: d_lon = 360.0 / CPR_NL(latitude) - m = math.floor(self.cfg_longitude / d_lon) + math.floor((self.cfg_longitude % d_lon) / d_lon - lon_cpr + 0.5) + m = int(math.floor(self.cfg_longitude / d_lon)) + \ + int(math.floor((self.cfg_longitude % d_lon) / d_lon - lon_cpr + 0.5)) longitude = d_lon * (m + lon_cpr) self.data['latitude'] = str(round(latitude, 3)) if latitude != 0.0 else "" self.data['longitude'] = str(round(longitude, 3)) if longitude != 0.0 else "" + basic.statistics['max_lat'] = max(basic.statistics['max_lat'], self.data['latitude']) + basic.statistics['min_lat'] = min(basic.statistics['min_lat'], self.data['latitude']) + basic.statistics['max_lon'] = max(basic.statistics['max_lon'], self.data['longitude']) + basic.statistics['min_lon'] = min(basic.statistics['min_lon'], self.data['longitude']) + return True def decodeCPR(self): @@ -332,13 +338,15 @@ def decodeCPR(self): if self.odd_time == 0 or self.even_time == 0: return False # we need both even + odd messages + if abs(self.odd_time - self.even_time) > 10.0: + return False # Need to have odd and even messages within 10 seconds cpr_lat_even = self.even_raw_latitude / self.MAX_17_BITS cpr_lon_even = self.even_raw_longitude / self.MAX_17_BITS cpr_lat_odd = self.odd_raw_latitude / self.MAX_17_BITS cpr_lon_odd = self.odd_raw_longitude / self.MAX_17_BITS - j = math.floor(59 * cpr_lat_even - 60 * cpr_lat_odd + 0.5) # latitude index + j = int(math.floor(59 * cpr_lat_even - 60 * cpr_lat_odd + 0.5)) # latitude index latitude_even = float((360.0 / 60.0) * (j % 60 + cpr_lat_even)) latitude_odd = float((360.0 / 59.0) * (j % 59 + cpr_lat_odd)) @@ -358,12 +366,12 @@ def decodeCPR(self): if self.even_time >= self.odd_time: ni = max(CPR_NL(latitude_even), 1) dlon = 360.0 / ni - m = math.floor(cpr_lon_even * (CPR_NL(latitude_even) - 1) - cpr_lon_odd * CPR_NL(latitude_even) + 0.5) + m = int(math.floor(cpr_lon_even * (CPR_NL(latitude_even) - 1) - cpr_lon_odd * CPR_NL(latitude_even) + 0.5)) longitude = dlon * (m % ni + cpr_lon_even) else: ni = max(CPR_NL(latitude_odd) - 1, 1) dlon = 360.0 / ni - m = math.floor(cpr_lon_even * (CPR_NL(latitude_odd) - 1) - cpr_lon_odd * CPR_NL(latitude_odd) + 0.5) + m = int(math.floor(cpr_lon_even * (CPR_NL(latitude_odd) - 1) - cpr_lon_odd * CPR_NL(latitude_odd) + 0.5)) longitude = dlon * (m % ni + cpr_lon_odd) if longitude >= 180: @@ -372,6 +380,11 @@ def decodeCPR(self): self.data['latitude'] = str(round(latitude, 3)) if latitude != 0.0 else "" self.data['longitude'] = str(round(longitude, 3)) if longitude != 0.0 else "" + basic.statistics['max_lat'] = max(basic.statistics['max_lat'], self.data['latitude']) + basic.statistics['min_lat'] = min(basic.statistics['min_lat'], self.data['latitude']) + basic.statistics['max_lon'] = max(basic.statistics['max_lon'], self.data['longitude']) + basic.statistics['min_lon'] = min(basic.statistics['min_lon'], self.data['longitude']) + # Reset all flags self.odd_time = 0 self.even_time = 0