class SuperSID(): ''' This is the main class which creates all other objects. In CMV pattern, this is the Controller. ''' running = False # class attribute to indicate if the SID application is running def __init__(self, config_file='', read_file=None): self.version = "EG 1.4 20150801" self.timer = None self.sampler = None self.viewer = None # Read Config file here print("Reading supersid.cfg ...", end='') # this script accepts a .cfg file as optional argument else we default # so that the "historical location" or the local path are explored self.config = Config(config_file or "supersid.cfg") # once the .cfg read, some sanity checks are necessary self.config.supersid_check() if not self.config.config_ok: print("ERROR:", self.config.config_err) exit(1) else: print(self.config.filenames) # good for debugging: what .cfg file(s) were actually read self.config["supersid_version"] = self.version # Create Logger - Logger will read an existing file if specified as -r|--read script argument self.logger = Logger(self, read_file) if 'utc_starttime' not in self.config: self.config['utc_starttime'] = self.logger.sid_file.sid_params["utc_starttime"] # Create the viewer based on the .cfg specification (or set default): # Note: the list of Viewers can be extended provided they implement the same interface if self.config['viewer'] == 'wx' and wx_imported: # GUI Frame to display real-time VLF Spectrum based on wxPython self.viewer = wxSidViewer(self) elif self.config['viewer'] == 'tk': # GUI Frame to display real-time VLF Spectrum based on tkinter (python 2 and 3) self.viewer = tkSidViewer(self) elif self.config['viewer'] == 'text': # Lighter text version a.k.a. "console mode" self.viewer = textSidViewer(self) else: print("ERROR: Unknown viewer", sid.config['viewer']) exit(2) # Assign desired psd function for calculation after capture # currently: using matplotlib's psd if (self.config['viewer'] == 'wx' and wx_imported) or self.config['viewer'] == 'tk': self.psd = self.viewer.get_psd # calculate psd and draw result in one call else: self.psd = mlab_psd # calculation only # calculate Stations' buffer_size self.buffer_size = int(24*60*60 / self.config['log_interval']) # Create Sampler to collect audio buffer (sound card or other server) self.sampler = Sampler(self, audio_sampling_rate = self.config['audio_sampling_rate'], NFFT = 1024); if not self.sampler.sampler_ok: self.close() exit(3) else: self.sampler.set_monitored_frequencies(self.config.stations); # Link the logger.sid_file.data buffers to the config.stations for ibuffer, station in enumerate(self.config.stations): station['raw_buffer'] = self.logger.sid_file.data[ibuffer] # Create Timer self.viewer.status_display("Waiting for Timer ... ") self.timer = SidTimer(self.config['log_interval'], self.on_timer) def clear_all_data_buffers(self): """Clear the current memory buffers and pass to the next day""" self.logger.sid_file.clear_buffer(next_day = True) def on_timer(self): """Callback function triggered by SidTimer every 'log_interval' seconds""" # current_index is the position in the buffer calculated from current UTC time current_index = self.timer.data_index utc_now = self.timer.utc_now # Get new data and pass them to the View message = "%s [%d] Capturing data..." % (self.timer.get_utc_now(), current_index) self.viewer.status_display(message, level=1) signal_strengths = [] try: data = self.sampler.capture_1sec() # return a list of 1 second signal strength Pxx, freqs = self.psd(data, self.sampler.NFFT, self.sampler.audio_sampling_rate) for binSample in self.sampler.monitored_bins: signal_strengths.append(Pxx[binSample]) except IndexError as idxerr: print("Index Error:", idxerr) print("Data len:", len(data)) except TypeError as err_te: print("Warning:", err_te) # ensure that one thread at the time accesses the sid_file's' buffers with self.timer.lock: # do we need to save some files (hourly) or switch to a new day? if self.timer.utc_now.minute == 0 and self.timer.utc_now.second < self.config['log_interval']: if self.config['hourly_save'] == 'YES': fileName = "hourly_current_buffers.raw.ext.%s.csv" % (self.logger.sid_file.sid_params['utc_starttime'][:10]) self.save_current_buffers(filename=fileName, log_type='raw', log_format='supersid_extended') # a new day! if self.timer.utc_now.hour == 0: # use log_type and log_format(s) requested by the user in the .cfg for log_format in self.config['log_format'].split(','): self.save_current_buffers(log_type=self.config['log_type'], log_format=log_format) self.clear_all_data_buffers() # Save signal strengths into memory buffers ; prepare message for status bar message = self.timer.get_utc_now() + " [%d] " % current_index for station, strength in zip(self.config.stations, signal_strengths): station['raw_buffer'][current_index] = strength message += station['call_sign'] + "=%f " % strength self.logger.sid_file.timestamp[current_index] = utc_now # end of this thread/need to handle to View to display captured data & message self.viewer.status_display(message, level=2) def save_current_buffers(self, filename='', log_type='raw', log_format = 'both'): ''' Save buffer data from logger.sid_file log_type = raw or filtered log_format = sid_format|sid_extended|supersid_format|supersid_extended|both|both_extended''' filenames = [] if log_format.startswith('both') or log_format.startswith('sid'): fnames = self.logger.log_sid_format(self.config.stations, '', log_type=log_type, extended=log_format.endswith('extended')) # filename is '' to ensure one file per station filenames += fnames if log_format.startswith('both') or log_format.startswith('supersid'): fnames = self.logger.log_supersid_format(self.config.stations, filename, log_type=log_type, extended=log_format.endswith('extended')) filenames += fnames return filenames def on_close(self): self.close() def run(self, wx_app = None): """Start the application as infinite loop accordingly to need""" self.__class__.running = True self.viewer.run() def close(self): """Call all necessary stop/close functions of children objects""" self.__class__.running = False if self.sampler: self.sampler.close() if self.timer: self.timer.stop() if self.viewer: self.viewer.close() def about_app(self): """return a text indicating various information on the app, incl, versions""" msg = """This program is designed to detect Sudden Ionosphere Disturbances (SID), \ which are caused by a blast of intense X-ray radiation when there is a Solar Flare on the Sun.\n\n""" + \ "Controller: " + self.version + "\n" + \ "Sampler: " + self.sampler.version + "\n" \ "Timer: " + self.timer.version + "\n" \ "Config: " + self.config.version + "\n" \ "Logger: " + self.logger.version + "\n" \ "Sidfile: " + self.logger.sid_file.version + "\n" + \ "Viewer: " + self.viewer.version + "\n" + \ "\n\nAuthor: Eric Gibert [email protected]" + \ "\n\nVisit http://solar-center.stanford.edu/SID/sidmonitor/ for more information." return msg
class SuperSID(): ''' This is the main class which creates all other objects. In CMV pattern, this is the Controller. ''' running = False # class attribute to indicate if the SID application is running def __init__(self, config_file='', read_file=None): self.version = "EG 1.4 20150801" self.timer = None self.sampler = None self.viewer = None # Read Config file here print("Reading supersid.cfg ...", end='') # this script accepts a .cfg file as optional argument else we default # so that the "historical location" or the local path are explored self.config = Config(os.path.expanduser(config_file) or "supersid.cfg") # once the .cfg read, some sanity checks are necessary self.config.supersid_check() if not self.config.config_ok: print("ERROR:", self.config.config_err) exit(1) else: print(self.config.filenames ) # good for debugging: what .cfg file(s) were actually read self.config["supersid_version"] = self.version # Create Logger - Logger will read an existing file if specified as -r|--read script argument self.logger = Logger(self, read_file) if 'utc_starttime' not in self.config: self.config['utc_starttime'] = self.logger.sid_file.sid_params[ "utc_starttime"] # Create the viewer based on the .cfg specification (or set default): # Note: the list of Viewers can be extended provided they implement the same interface if self.config['viewer'] == 'wx': # GUI Frame to display real-time VLF Spectrum based on wxPython # # special case: 'wx' module might not be installed (text mode only) nor even available (python 3) try: from wxsidviewer import wxSidViewer self.viewer = wxSidViewer(self) wx_imported = True except ImportError: print("'wx' module not imported.") wx_imported = False elif self.config['viewer'] == 'tk': # GUI Frame to display real-time VLF Spectrum based on tkinter (python 2 and 3) from tksidviewer import tkSidViewer self.viewer = tkSidViewer(self) elif self.config['viewer'] == 'text': # Lighter text version a.k.a. "console mode" from textsidviewer import textSidViewer self.viewer = textSidViewer(self) else: print("ERROR: Unknown viewer", sid.config['viewer']) exit(2) # Assign desired psd function for calculation after capture # currently: using matplotlib's psd if (self.config['viewer'] == 'wx' and wx_imported) or self.config['viewer'] == 'tk': self.psd = self.viewer.get_psd # calculate psd and draw result in one call else: self.psd = mlab_psd # calculation only # calculate Stations' buffer_size self.buffer_size = int(24 * 60 * 60 / self.config['log_interval']) # Create Sampler to collect audio buffer (sound card or other server) self.sampler = Sampler( self, audio_sampling_rate=self.config['audio_sampling_rate'], NFFT=1024) if not self.sampler.sampler_ok: self.close() exit(3) else: self.sampler.set_monitored_frequencies(self.config.stations) # Link the logger.sid_file.data buffers to the config.stations for ibuffer, station in enumerate(self.config.stations): station['raw_buffer'] = self.logger.sid_file.data[ibuffer] # Create Timer self.viewer.status_display("Waiting for Timer ... ") self.timer = SidTimer(self.config['log_interval'], self.on_timer) def clear_all_data_buffers(self): """Clear the current memory buffers and pass to the next day""" self.logger.sid_file.clear_buffer(next_day=True) def on_timer(self): """Callback function triggered by SidTimer every 'log_interval' seconds""" # current_index is the position in the buffer calculated from current UTC time current_index = self.timer.data_index utc_now = self.timer.utc_now # Get new data and pass them to the View message = "%s [%d] Capturing data..." % (self.timer.get_utc_now(), current_index) self.viewer.status_display(message, level=1) signal_strengths = [] try: data = self.sampler.capture_1sec( ) # return a list of 1 second signal strength Pxx, freqs = self.psd(data, self.sampler.NFFT, self.sampler.audio_sampling_rate) for binSample in self.sampler.monitored_bins: signal_strengths.append(Pxx[binSample]) except IndexError as idxerr: print("Index Error:", idxerr) print("Data len:", len(data)) except TypeError as err_te: print("Warning:", err_te) # ensure that one thread at the time accesses the sid_file's' buffers with self.timer.lock: # do we need to save some files (hourly) or switch to a new day? if self.timer.utc_now.minute == 0 and self.timer.utc_now.second < self.config[ 'log_interval']: if self.config['hourly_save'] == 'YES': fileName = "hourly_current_buffers.raw.ext.%s.csv" % ( self.logger.sid_file.sid_params['utc_starttime'][:10]) self.save_current_buffers(filename=fileName, log_type='raw', log_format='supersid_extended') # a new day! if self.timer.utc_now.hour == 0: # use log_type and log_format(s) requested by the user in the .cfg for log_format in self.config['log_format'].split(','): self.save_current_buffers( log_type=self.config['log_type'], log_format=log_format) self.clear_all_data_buffers() # Save signal strengths into memory buffers ; prepare message for status bar message = self.timer.get_utc_now() + " [%d] " % current_index for station, strength in zip(self.config.stations, signal_strengths): station['raw_buffer'][current_index] = strength message += station['call_sign'] + "=%f " % strength self.logger.sid_file.timestamp[current_index] = utc_now # end of this thread/need to handle to View to display captured data & message self.viewer.status_display(message, level=2) def save_current_buffers(self, filename='', log_type='raw', log_format='both'): ''' Save buffer data from logger.sid_file log_type = raw or filtered log_format = sid_format|sid_extended|supersid_format|supersid_extended|both|both_extended''' filenames = [] if log_format.startswith('both') or log_format.startswith('sid'): fnames = self.logger.log_sid_format( self.config.stations, '', log_type=log_type, extended=log_format.endswith('extended') ) # filename is '' to ensure one file per station filenames += fnames if log_format.startswith('both') or log_format.startswith('supersid'): fnames = self.logger.log_supersid_format( self.config.stations, filename, log_type=log_type, extended=log_format.endswith('extended')) filenames += fnames return filenames def on_close(self): self.close() def run(self, wx_app=None): """Start the application as infinite loop accordingly to need""" self.__class__.running = True self.viewer.run() def close(self): """Call all necessary stop/close functions of children objects""" self.__class__.running = False if self.sampler: self.sampler.close() if self.timer: self.timer.stop() if self.viewer: self.viewer.close() def about_app(self): """return a text indicating various information on the app, incl, versions""" msg = """This program is designed to detect Sudden Ionosphere Disturbances (SID), \ which are caused by a blast of intense X-ray radiation when there is a Solar Flare on the Sun.\n\n""" + \ "Controller: " + self.version + "\n" + \ "Sampler: " + self.sampler.version + "\n" \ "Timer: " + self.timer.version + "\n" \ "Config: " + self.config.version + "\n" \ "Logger: " + self.logger.version + "\n" \ "Sidfile: " + self.logger.sid_file.version + "\n" + \ "Viewer: " + self.viewer.version + "\n" + \ "\n\nAuthor: Eric Gibert [email protected]" + \ "\n\nVisit http://solar-center.stanford.edu/SID/sidmonitor/ for more information." return msg
class SuperSID_scanner(): ''' This is the main class which creates all other objects. In CMV pattern, this is the Controller. ''' running = False # class attribute to indicate if the SID application is running def __init__(self, config_file='', scan_params=(15, 16000, 24000)): self.version = "1.3.1 20130910" self.timer = None self.sampler = None self.viewer = None # Read Config file here print("Reading supersid.cfg ...", end='') # this script accepts a .cfg file as optional argument else we default # so that the "historical location" or the local path are explored self.config = Config(config_file or "supersid.cfg") # once the .cfg read, some sanity checks are necessary self.config.supersid_check() if not self.config.config_ok: print("ERROR:", self.config.config_err) exit(1) else: print(self.config.filenames ) # good for debugging: what .cfg file(s) were actually read (self.scan_duration, self.scan_from, self.scan_to) = scan_params print("Scanning for %d minutes on [%d:%d]..." % scan_params) # create an artificial list of stations self.config.stations = [] for freq in range(self.scan_from, self.scan_to + 100, 100): new_station = {} new_station['call_sign'] = "ST_%d" % freq new_station['frequency'] = str(freq) self.config.stations.append(new_station) # Create Logger - Logger will read an existing file if specified as -r|--read script argument self.logger = Logger(self, '') if 'utc_starttime' not in self.config: self.config['utc_starttime'] = self.logger.sid_file.sid_params[ "utc_starttime"] # Create the viewer based on the .cfg specification (or set default): # Note: the list of Viewers can be extended provided they implement the same interface self.config[ 'viewer'] = 'text' # Lighter text version a.k.a. "console mode" self.viewer = textSidViewer(self) self.psd = mlab_psd # calculation only # calculate Stations' buffer_size self.buffer_size = int(24 * 60 * 60 / self.config['log_interval']) # Create Sampler to collect audio buffer (sound card or other server) self.sampler = Sampler( self, audio_sampling_rate=self.config['audio_sampling_rate'], NFFT=1024) if not self.sampler.sampler_ok: self.close() exit(3) else: self.sampler.set_monitored_frequencies(self.config.stations) # Link the logger.sid_file.data buffers to the config.stations for ibuffer, station in enumerate(self.config.stations): station['raw_buffer'] = self.logger.sid_file.data[ibuffer] # Create Timer self.viewer.status_display("Waiting for Timer ... ") self.timer = SidTimer(self.config['log_interval'], self.on_timer, delay=2) self.scan_end_time = self.timer.start_time + 60 * self.scan_duration def clear_all_data_buffers(self): """Clear the current memory buffers and pass to the next day""" self.logger.sid_file.clear_buffer(next_day=True) def on_timer(self): """Callback function triggered by SidTimer every 'log_interval' seconds""" # current_index is the position in the buffer calculated from current UTC time current_index = self.timer.data_index utc_now = self.timer.utc_now # clear the View to prepare for new data display self.viewer.clear() # Get new data and pass them to the View message = "%s [%d] Capturing data..." % (self.timer.get_utc_now(), current_index) self.viewer.status_display(message, level=1) try: data = self.sampler.capture_1sec( ) # return a list of 1 second signal strength Pxx, freqs = self.psd(data, self.sampler.NFFT, self.sampler.audio_sampling_rate) except IndexError as idxerr: print("Index Error:", idxerr) print("Data len:", len(data)) signal_strengths = [] for binSample in self.sampler.monitored_bins: signal_strengths.append(Pxx[binSample]) # ensure that one thread at the time accesses the sid_file's' buffers with self.timer.lock: # Save signal strengths into memory buffers ; prepare message for status bar message = self.timer.get_utc_now() + " [%d] " % current_index message += "%d" % (self.scan_end_time - self.timer.time_now) for station, strength in zip(self.config.stations, signal_strengths): station['raw_buffer'][current_index] = strength self.logger.sid_file.timestamp[current_index] = utc_now # did we complete the expected scanning duration? if self.timer.time_now >= self.scan_end_time: fileName = "scanner_buffers.raw.ext.%s.csv" % ( self.logger.sid_file.sid_params['utc_starttime'][:10]) fsaved = self.save_current_buffers( filename=fileName, log_type='raw', log_format='supersid_extended') print(fsaved, "saved.") self.close() exit(0) # end of this thread/need to handle to View to display captured data & message self.viewer.status_display(message, level=2) def save_current_buffers(self, filename='', log_type='raw', log_format='both'): """ Save buffer data from logger.sid_file log_type = raw or filtered log_format = sid_format|sid_extended|supersid_format|supersid_extended|both|both_extended""" filenames = [] if log_format.startswith('both') or log_format.startswith('sid'): fnames = self.logger.log_sid_format( self.config.stations, '', log_type=log_type, extended=log_format.endswith('extended') ) # filename is '' to ensure one file per station filenames += fnames if log_format.startswith('both') or log_format.startswith('supersid'): fnames = self.logger.log_supersid_format( self.config.stations, filename, log_type=log_type, extended=log_format.endswith('extended')) filenames += fnames return filenames def on_close(self): self.close() def run(self, wx_app=None): """Start the application as infinite loop accordingly to need""" self.__class__.running = True if self.config['viewer'] == 'wx': wx_app.MainLoop() elif self.config['viewer'] == 'text': try: while (self.__class__.running): sleep(1) except (KeyboardInterrupt, SystemExit): pass def close(self): """Call all necessary stop/close functions of children objects""" if self.sampler: self.sampler.close() if self.timer: self.timer.stop() if self.viewer: self.viewer.close() self.__class__.running = False