def trigger(self): if self.triggerCheck(): self.triggered = True errors.debug(self.getID()+" triggered.") return False else: return True
def trigger(self,triggerer,objects): if self.triggerCheck(): for Object in objects: if Object.getID()==self.target: Object.setState(self.newState) errors.debug(self.getID()+" triggered.") self.triggered = True
def build_file_for_dash(self): index = 1 #WRITE DATA FOR DASHBOARD: errors.debug("Building converted data file for dash.") try: #this file will be read using pysftp by the dashboard application whenever it loads with open("data_for_dash", 'a') as fordash: #open format file and data file with open("dashformat.txt", 'r') as dashformat: for line in dashformat: #for each line in format file if line[: 4] == "STR:": #if the line is a string tag, copy it over fordash.write("CVT\t" + line[4:].strip() + '\n') else: #if it's a label, copy it over with data appended fordash.write(line.strip() + '\t' + self.data_list[index] + '\n') index += 1 except IndexError: if not self.for_dash_error: errors.error( "Error in parsing dashformat.txt; file may have been " + "corrupted. Dashboard may not display data properly or correctly." + " Error message: " + traceback.format_exc(Config.TRACEBACK_LIMIT)) else: errors.debug( "Error in writing data_for_dash again, don't trust dashboard." ) self.for_dash_error = True self.data_list = []
def maybe_end(self): #checks if end signal has been received, and ends program if it has if self.endToggle: #this toggle is set by the signal handler below errors.info("Received end signal, terminating main.py. " + #if ending, send email "Use startup.sh to restart.") errors.sendEmail(subject="TS-4200 Stopping Operations") errors.debug("main.py exiting.") raise SystemExit #exit python os.system("sudo ts4200ctl --redledon") #turn on LED self.note_running() #leave a timestamp for dash
def trigger(self,triggerer,objects): if self.triggerCheck(): for Object in objects: if Object.getID()==self.target: if Object.getState()==self.primaryState: Object.setState(self.secondaryState) else: Object.setState(self.primaryState) errors.debug(self.getID()+" triggered.") self.triggered = True
def daily_checks(self): """ Checks that various parts of the system are healthy: the PIC is not missing strings repeatedly, the board has enough space and memory to run properly. Also resets all error flags. Runs only every 4320 cycles (24 hours @ 20s/cycle). """ self.build_file_for_dash() #put together data for GUI errors.debug("daily_checks called.") os.system("sudo ts4200ctl --redledoff") #turn off LED self.cycles +=1 if self.cycles != 1: #i.e., run only every 24 hours' worth of cycles errors.debug("daily_checks exiting without checking.") return errors.debug("Performing daily checks.") self.cycles = 0 #reset counter #the following 2 checks use the statvfs utility. see statvfs man pages. #check space remaining on local filesystem: localStats = os.statvfs(Config.TO_UNIX_FILE_PATH) #get statvfs data about local filesystem freeBytes = localStats.f_frsize * localStats.f_bavail #free bytes = fragment size * number of fragments available to us if freeBytes < Config.FREE_BYTES_WARNING: #check that this number is acceptable. if not, send an alert. errors.error("TS-4200 has " + str(freeBytes/1000000) + " MB remaining. " + "This is a serious and unforeseen error. Immediate corrective action " + "recommended to determine what is filling up the local filesystem.") #check space remaining on flash drive if os.path.ismount(Config.FLASH_BACKUP_DIREC_PATH): #only try if flash drive is mounted usbStats = os.statvfs(Config.FLASH_BACKUP_DIREC_PATH) #same as previous, except passing statvfs the USB mount point freeBytes = usbStats.f_frsize * usbStats.f_bavail if freeBytes < Config.FREE_BYTES_WARNING: errors.error("Flash drive has " + str(freeBytes/1000000) + " MB remaining. " + "Freya will fill this at a rate of about 15MB/week. Replace flash drive.") #check RAM usage with open("/proc/meminfo", 'r') as meminfo: #open /proc/meminfo, a special file holding info about RAM usage meminfo.readline() #extract statistics from successive lines of /proc/meminfo freemem = int(meminfo.readline().split()[1]) #unused memory is best estimated as "free memory" plus buffered and buffermem = int(meminfo.readline().split()[1]) #cached memory, since these are available to be overwritten if a cachemem = int(meminfo.readline().split()[1]) #process requires them. unusedmem = freemem + buffermem + cachemem if unusedmem < Config.UNUSED_MEMORY_WARNING: #send warning if memory usage is too high errors.error("TS-4200 has only " + str(unusedmem) + " kB of " + "unused RAM. Speed and performance may be impacted, corrective action recommended.") PIC_bad_strings = self.reader.numBadStrings(reset=True) #get number of bad strings and reset all flags in reader if PIC_bad_strings >= Config.TOTAL_BAD_STRING_WARNING: #send an error if there are too many bad strings errors.info("TS-4200 has logged " + str(PIC_bad_strings) + " bad strings " + "(unrecognized ID, wrong length, or unreceived) in last 24 hours. Probable " + "mismatch between PIC format and expected format. Be sure that picdata.conf " + "is updated to fit what the PIC is sending. Warnings should have been sent " + "when problems first happened -- check these for more information on what " + "might be going wrong.") else: errors.debug("Only " + str(PIC_bad_strings) + " bad strings for the day. No warning sent.") convert.reset_all_flags() #resets all data converter error flags self.outputter.reset() #resets outputter's error flags self.sched_warning_sent = False #reset schedule error flag open("text_for_dash", 'w').close() #stop text_for_dash from ballooning
def __init__(self): """Parses outdata.conf into a list, makes a document holding format of data to dashboard, and initializes error flags.""" self.format = [] #this list holds labels of outgoing data in order try: open("dashformat.txt", 'w').close() #clear dashformat.txt with open("dashformat.txt", 'a') as dashformat: #open dashformat.txt with open("outdata.conf", 'r') as outdata: #open outdata.conf for line in outdata: #iterate through each line if line == '\n' or line[ 0] == "#": #ignore empty lines or lines that start with '#' pass elif line[:4] == "STR:": dashformat.write(line) else: #append what's left to self.format and dashformat.txt self.format.append(line.strip('\n')) dashformat.write(line) #in the case of errors, send a message and then exit the program: except IOError: #error in finding or reading file (if thrown, most likely outdata.conf does not exist) errors.error( "FATAL: DataOutputter unable to read outdata.conf. " + "TS-4200 will not be able to output data until fixed. " + "main.py exiting. Error message: " + traceback.format_exc(Config.TRACEBACK_LIMIT)) errors.sendEmail() errors.debug("Preparing to exit...") sys.exit("Unable to read outdata.conf") except: #unexpected error of some other sort errors.error( "FATAL: unexpected error in parsing outdata.conf. main.py " + "exiting. Error message: " + traceback.format_exc(Config.TRACEBACK_LIMIT)) errors.sendEmail() errors.debug("Preparing to exit...") sys.exit("Unable to parse outdata.conf") self.data = {} #a dictionary keying label to converted data self.unrecognized_labels = [] #initialize error flags/lists: self.missing_data = [] self.to_unix_error = False self.temp_backup_error = False self.flash_file_error = False self.flash_drive_error = False self.for_dash_error = False open("temp_unix_backup", 'w').close() #initialize backup file
def send_commands(self): """ Reads files holding all commands for the PIC and sends them. Command file should be formatted as <cmd><tab><source>, with one command per line. """ errors.debug("Preparing to send commands to PIC.") try: with open(Config.COMMAND_FILE_PATH, 'r') as command_file: for line in command_file: command = line.split('\t') errors.debug(str(command)) if len(command) > 2: self.port.write(command[0].strip()) if command[1].strip() == "DASH": errors.info("TS-4200 sent command " + command[0] + " from source " + command[1].strip() + " to PIC via serial.") else: errors.debug("Sent command " + command[0] + " from source " + command[1].strip()) except: if not self.command_error: self.command_error = True errors.error( "Error in reading and sending commands. Error message: " + traceback.format_exc(Config.TRACEBACK_LIMIT)) else: open(Config.COMMAND_FILE_PATH, 'w').close()
def build_file_for_dash( self): #overwrites the file for dash with raw data. errors.debug("Data reader building file of raw data for dash board...") with open( 'picdata.conf.cache', 'r' ) as picdata: #dataoutputter will pick this file up and append converted data to it with open('data_for_dash', 'w') as fordash: fordash.write(str(time.time()) + '\n') for line in picdata: line = line.strip() if line == "" or line[0] == "#": pass elif len(line) > 4 and line[:4] == "STR:": fordash.write("RAW\t" + line[4:].strip() + '\n') else: try: raw = self.get(line) except DataMissingError: raw = "not received" fordash.write(line + '\t' + raw + '\n') #print line + '\t' + raw + '\n' errors.debug("Done building raw data file for dash.")
def __init__(self): """ Constructor for WeatherScheduler. Responsible for initializing instances of DataReader and DataOutputter and setting these to be used by DataConverters. Also initializes a scheduler from python's sched module. """ self.startup_time = time.time() #get the current time t = "unknown" try: with open(Config.ONELINE_PATH, 'r') as last_time: t = float(last_time.read().split('\t')[0]) #get the last time logged except: t = "unknown" try: downtime = int((self.startup_time - t)/60) #calculate and alert how long Freya has been down errors.info("main.py restarting after " + str(downtime) + " minutes' downtime.") except TypeError: pass errors.debug("WeatherScheduler initializing.") self.reader = DataReader() #initialize a DataReader to handle raw data self.outputter = DataOutputter() #initialize a DataOutputter to handle converted data #see handleData for details on these DataConverters.DataConverter.set_in_out( #tell all DataConverters to use the objects just created reader=self.reader, outputter=self.outputter) self.scheduler = sched.scheduler(time.time, time.sleep) #create a scheduler to handle the main loop's timing self.cycles = 0 #this counter will be used to tell daily_checks when to run self.sched_warning_sent = False #remember if a falling-behind-schedule warning has already been sent self.endToggle = False #this is set to true when a signal is received, and allows the #program to exit gracefully at the end of a cycle signal.signal(signal.SIGUSR1, self.end) #initialize a signal handler to catch SIGUSR1 and call self.end() errors.debug("Set signal handler for SIGUSR1 to " + str(signal.getsignal(signal.SIGUSR1))) self.maybe_end()
def run(self): """ This function waits until safe to get data from PIC, then runs the main loop forever, calling other functions as scheduled. """ still_to_wait = int(Config.WAIT_AT_STARTUP - (time.time() - self.startup_time)) #calculate how much longer to wait errors.debug("Waiting " + str(still_to_wait) + " seconds for PIC...") for i in range(int(still_to_wait/20)): #wait, but periodically check if end signal has been received for j in range(19): time.sleep(1) self.maybe_end() errors.debug("Entering main loop.") self.last_cycle_started = time.time() while True: errors.debug("____________NEW CYCLE____________") errors.sendEmail() self.note_running() os.environ['TZ'] = 'CST+6' time.tzset() local_time = time.localtime() unix_time = str(time.time()) year = str(local_time.tm_year) month = str(local_time.tm_mon) day = str(local_time.tm_mday) hour = str(local_time.tm_hour) minute = str(local_time.tm_min) second = str(local_time.tm_sec) clk_source = Config.NULL_VAL stardate = Config.NULL_VAL future = Config.NULL_VAL header = [unix_time, year, month, day, hour, minute, second, clk_source, stardate, future] header_1 = [unix_time, year, month, day, hour, minute, second, clk_source, stardate, future] header_2 = [unix_time, year, month, day, hour, minute, second, clk_source, stardate, future] toggleFile = open(Config.TOGGLE_FILE_PATH, 'r') toggle = toggleFile.readline() if toggle == '': toggle = 0 toggleFile.close() self.reader.read(header, header_1, int(toggle)) convert.process_all() self.outputter.save(header_2, int(toggle)) toggleFile = open(Config.TOGGLE_FILE_PATH, 'r') last_toggle = int(toggleFile.readline()) toggleFile.close() toggleFile = open(Config.TOGGLE_FILE_PATH, 'w') if (last_toggle == 1 and int(toggle) == 0): toggleFile.write('1') else: toggleFile.write('0') toggleFile.close() self.reader.send_commands() self.daily_checks() self.finish_cycle() self.scheduler.run()
def finish_cycle(self): """Called at the end of a cycle to wrap up""" self.maybe_end() #see above time_to_sleep = self.last_cycle_started + 29.9787 - time.time() #calculate time to sleep. 19.9787 is 20s minus the average #time it takes to queue the next cycle if time_to_sleep < Config.BEHIND_SCHEDULE_WARNING: #if behind schedule, send an alert and don't sleep if not self.sched_warning_sent: errors.info("main.py is falling behind schedule. Last cycle ended " + str(time_to_sleep)[1:] + " seconds late. Corrective action recommended.") self.sched_warning_sent = True else: errors.debug("Not sleeping before next cycle, already behind " +str(time_to_sleep)[1:]+ " seconds.") elif time_to_sleep < 0: errors.debug("Not sleeping before next cycle, already behind " +str(time_to_sleep)[1:]+ " seconds.") else: errors.debug("Seconds to next cycle: " + str(time_to_sleep)) #sleep until time for next cycle time.sleep(time_to_sleep) self.last_cycle_started = time.time()
def reset_all_flags( ): #resets flags for all converters. called from main.py daily. debug( "reset_all_flags called; resetting all data converter error flags...") for converter in all_converters: converter.reset_flags()
def end(self, signum, frame): #this method catches the SIGUSR1 signal sent by end.sh and errors.debug("Received SIGUSR1.") #sets endToggle to true. endToggle will be checked periodically, and self.endToggle = True #if it is true the program ends (see above).
def send(self, cmd): """Sends string cmd to PIC via serial connection.""" #flush input so that input doesn't build up in serial port -- self.port.write(cmd) #we want only data sent in response to S errors.debug("Sent " + cmd + " to PIC.")
def note_running(self): """Leave a timestamp in a file so GUI knows Freya is running""" with open("board_is_running", 'w') as b: #write the current time to a file b.write(str(time.time())) errors.debug("Updated timestamp for dash.")
def read(self, header, header_1, toggle): """ Reads strings of raw data from serial port and checks them for errors before saving them in a dictionary. The strings that make it through this method are guaranteed to have the right ID and length, but any given piece of data could conceivably be missing (""). Checking that data exist is the job of get(). """ raw_data_list = header raw_array_data_list = header_1 string_list = [] self.dataStrings = {} #clearing data errors.debug("read() called, getting data...") strings = [] print str( self.numStrings()) + " from read() in DataReader" #ENTER LOOP self.port.write("SS") string_B = self.port.readline(size=None, eol='\r') time.sleep(1.1) string_B = string_B.split('\t') string_list.append(string_B) self.port.write("QQ") self.port.write("11") time.sleep(7) string_1 = self.port.readline(size=None, eol='\r') time.sleep(1.1) string_1 = string_1.split('\t') string_list.append(string_1) self.port.write("22") string_2 = self.port.readline(size=None, eol='\r') time.sleep(1.1) string_2 = string_2.split('\t') string_list.append(string_2) self.port.write("33") string_3 = self.port.readline(size=None, eol='\r') time.sleep(1.1) string_3 = string_3.split('\t') string_list.append(string_3) self.port.write("44") string_4 = self.port.readline(size=None, eol='\r') time.sleep(1.1) string_4 = string_4.split('\t') string_list.append(string_4) for i in range(len(string_list)): string = string_list[i] badstring = 1 if string == "": #if data read is empty... self.totalEmptyStrings += 1 #increment the relevant counters, and self.consecutiveEmptyStrings += 1 errors.debug("Received empty string from PIC.") with open("pic_status", 'w') as pic_status: pic_status.write("0") if self.consecutiveEmptyStrings == Config.MISSED_CONSECUTIVE_STRING_WARNING: #send a warning if necessary downtime = int( (self.consecutiveEmptyStrings / 3) / self.numStrings()) errors.error("PIC has been unresponsive for " + str(downtime) + " minutes.") else: #if string is not empty... with open("pic_status", 'w') as pic_status: pic_status.write("1") if self.consecutiveEmptyStrings > Config.MISSED_CONSECUTIVE_STRING_WARNING: #send a notification that PIC is responsive, if necessary errors.error("Received string from PIC after " + str(self.consecutiveEmptyStrings) + " consecutive missed strings.") self.consecutiveEmptyStrings = 0 #reset consecutiveEmptyStrings counter #make the string into a list to better manipulate it stringID = string[0] #save the ID (the first item in the list) string = string[ 1:] #BUILD DATA STRING and check for missing data: #put each datum into a list print "self.expected_strings", self.expected_strings #remove the ID from the string if stringID not in self.expected_strings: #if the ID is not recognized... if not self.PICStringIDErrorState: #notify as appropriate errors.error( "Received unexpected string from PIC starting with " + stringID + ". Assume error continues until intervention. Data will be lost " + "if strings of the expected format are not received.") else: errors.debug( "Received bad string from PIC starting with " + stringID + ".") self.PICStringIDErrorState = True #and raise error flag else: #if ID is recognized... errors.debug("String of ID " + stringID + " found.") expectedLength = self.expected_strings[stringID] if len( string ) != expectedLength: #check if it is the expected length if not self.PICFormatErrorState: #if not, notify as appropriate errors.info( "Received bad string from PIC: " + str(len(string)) + " fields instead of " + str(expectedLength) + " fields in string " + stringID + ". Assume error continues until intervention. Data will be lost " + "if strings of the expected format are not received. Any strings not " + "conforming to expected format can not be processed." ) else: errors.debug("Received bad string from PIC: " + str(len(string)) + " fields instead of " + str(expectedLength) + " fields.") self.PICFormatErrorState = True #and raise error flag else: #if it is the expected length, then accept it errors.debug("String accepted.") self.dataStrings[ stringID] = string #add it to dictionary of data badstring = 0 #mark it as a good string self.totalBadStrings += badstring string_B = string_B[1:] string_1 = string_1[1:] string_2 = string_2[1:] string_3 = string_3[1:] string_4 = string_4[1:] len_string_1 = len(string_1) len_string_B = len(string_B) len_string_2 = len(string_2) len_string_3 = len(string_3) len_string_4 = len(string_4) exp_len_string_1 = self.expected_strings['1'] exp_len_string_B = self.expected_strings['B'] exp_len_string_2 = self.expected_strings['2'] exp_len_string_3 = self.expected_strings['3'] exp_len_string_4 = self.expected_strings['4'] if (len_string_1 == 0): for i in range(exp_len_string_1): string_1.append(str(Config.ERROR_VAL)) if (len_string_B == 0): for i in range(exp_len_string_B): string_B.append(str(Config.ERROR_VAL)) if (len_string_2 == 0): for i in range(exp_len_string_2): string_2.append(str(Config.ERROR_VAL)) if (len_string_3 == 0): for i in range(exp_len_string_3): string_3.append(str(Config.ERROR_VAL)) if (len_string_4 == 0): for i in range(exp_len_string_4): string_4.append(str(Config.ERROR_VAL)) for item in string_1: raw_data_list.append(item.strip()) for item in string_B: raw_data_list.append(item.strip()) for item in string_2: raw_array_data_list.append(item.strip()) for item in string_3: raw_array_data_list.append(item.strip()) for item in string_4: raw_array_data_list.append( item.strip() ) #add to counter if string was not good #and repeat raw_data_string = "\t".join(raw_data_list) + '\n' raw_array_data_string = "\t".join(raw_array_data_list) + '\n' if (not os.path.exists(Config.CUR_BACKUP_PATH)): os.makedirs(Config.CUR_BACKUP_PATH) if os.path.ismount(Config.FLASH_BACKUP_DIREC_PATH): try: errors.debug("Attempting to write rawdata backup") with open( Config.RAW_DATA_BACKUP_PATH, 'a') as raw_data_backup: #actually write data to file raw_data_backup.write(raw_data_string) errors.debug("...write successful.") self.to_unix_error = False except: errors.debug("Copy failed.") raw_data_backup.close() try: errors.debug("Attempting to write raw array data backup") with open( Config.RAW_ARRAY_DATA_BACKUP_PATH, 'a' ) as raw_array_data_backup: #actually write data to file raw_array_data_backup.write(raw_array_data_string) errors.debug("...write successful.") self.to_unix_error = False except: errors.debug("Copy failed.") raw_array_data_backup.close() if (toggle == 1): try: errors.debug("Attempting to write rawdata") with open(Config.RAW_DATA_FILE_PATH, 'w') as raw_data: #actually write data to file raw_data.write(raw_data_string) errors.debug("...write successful.") self.to_unix_error = False except: errors.debug("Copy failed.") raw_data.close() try: errors.debug("Attempting to write raw array data") with open(Config.RAW_ARRAY_DATA_FILE_PATH, 'w') as raw_array_data: #actually write data to file raw_array_data.write(raw_array_data_string) errors.debug("...write successful.") self.to_unix_error = False except: errors.debug("Copy failed.") raw_array_data.close() if (toggle == 0): try: errors.debug("Attempting to write rawdata") with open(Config.RAW_DATA_FILE_PATH, 'a') as raw_data: #actually write data to file raw_data.write(raw_data_string) errors.debug("...write successful.") self.to_unix_error = False except: errors.debug("Copy failed.") raw_data.close() try: errors.debug("Attempting to write raw array data") with open(Config.RAW_ARRAY_DATA_FILE_PATH, 'a') as raw_array_data: #actually write data to file raw_array_data.write(raw_array_data_string) errors.debug("...write successful.") self.to_unix_error = False except: errors.debug("Copy failed.") raw_array_data.close() #EXIT LOOP for stringID in self.expected_strings: #check that each expected string was received if stringID not in self.dataStrings: #if a string wasn't read, if stringID not in self.badFormatErrors: #and isn't already broken, errors.error( "Did not receive full string " + stringID + " from PIC. " + #send an alert "Assume error continues until otherwise notified. No data from " + stringID + " available.") self.badFormatErrors.append( stringID) #and add it to the list of broken strings else: errors.debug("Did not find string " + stringID + " again.") else: #if a string was read, make sure it isn't in the list of broken strings try: self.badFormatErrors.remove( stringID) #alert that it is working again errors.error( "Received previously missing string " + stringID + ". Assume problem solved until notified otherwise.") except: pass errors.debug("Done reading and storing data.")
def __init__(self): """ Constructor for DataReader. Responsible for initializing locations, a dictionary keying (string, index) by field label, from picdata.conf. If this fails, method will raise SystemExit. Also opens serial connection as configured in Config.py. If connection fails at first, method will continue attempting indefinitely. """ errors.debug("DataReader initializing...") self.locations = { } #this code reads picdata.conf and turns it into a dictionary, index = 0 #keyed by field label holding a tuple of string name and index in string (from 0): current_string = None #in essence, the dictionary created here asks for the string's label and gives its location. #it will be used to tell the DataReader where to look for data that is requested self.expected_strings = { } #expected_strings keys each string expected to its length errors.debug("Attempting to parse picdata.") try: copyfile('picdata.conf', 'picdata.conf.cache') with open( 'picdata.conf.cache', 'r') as picdata: #open the file containing expected format for line in picdata: #iterate though each line line = line.strip( '\n') #clean the line of return characters if line != '' and line[ 0] != "#": #skip empty lines and comments if len( line ) >= 4 and line[:4] == "STR:": #if "STR:" tag is present, register the start of a new string index = 0 #start counting index from zero current_string = line[4:].strip( ) #save string as current string errors.debug("Registered string " + current_string) else: #if "STR:" tag is not present, the line must contain the name of a field if line in self.locations: errors.info( "Warning: picdata.conf contains label " + line + " multiple times. Only the last instance will be kept." ) if current_string is None: errors.info( "Warning: picdata.conf is improperly formatted, " + "contains label without string header.") else: self.locations[line] = ( current_string, index ) #add an entry to the dictionary holding the string it is in and the index in that string index += 1 #increment the index for next time self.expected_strings[current_string] = index errors.debug("Parsed picdata.conf successfully.") for label in self.locations: ################################ print label, self.locations[ label], "\n" ################################ print "self.expected_strings ->", self.expected_strings #in the case of errors, send a message and then exit the program: except IOError: #error in finding or reading file (if thrown, most likely picdata.conf does not exist) errors.error( "FATAL: DataReader unable to read picdata.conf. " + "TS-4200 will not be able to interpret strings received from PIC until fixed. " + "main.py exiting. Error message: " + traceback.format_exc(Config.TRACEBACK_LIMIT)) errors.sendEmail() errors.debug("Preparing to exit...") sys.exit("Unable to read picdata.conf") except ( IndexError, KeyError ): #error in adding entry or slicing string (very unlikely, this is here just in case) errors.error( "FATAL: DataReader unable to parse picdata.conf. " + "Error is either in parser or in format of picdata.conf. main.py " + "exiting. Error message: " + traceback.format_exc(Config.TRACEBACK_LIMIT)) errors.sendEmail() errors.debug("Preparing to exit...") sys.exit("Unable to parse picdata.conf") except: #unexpected error of some other sort errors.error( "FATAL: unexpected error in parsing picdata.conf. main.py " + "exiting. Error message: " + traceback.format_exc(Config.TRACEBACK_LIMIT)) errors.sendEmail() errors.debug("Preparing to exit...") sys.exit("Unable to parse picdata.conf") self.connectionFailures = 0 while True: #try to establish connection to serial port: try: errors.debug("Attempting serial port connection...") self.port = serial.Serial( #create a Serial object to represent the port port=Config.PORT_NUM, baudrate=Config.BAUD_RATE, timeout=Config.PORT_TIMEOUT) if self.connectionFailures > 1: #alert to success if connection has failed previously errors.sendEmail( message= "Serial connection successful, initialization continuing.", subject="TS-4200 Serial Connection Successful") errors.log.error("Serial connection successful.") break #exit loop if successful except serial.SerialException: #at two fails, log and send an email self.connectionFailures += 1 if self.connectionFailures == 2 or self.connectionFailures % 4320 == 0: #at two fails or every 6 hours, log and send an email errors.sendEmail( message="Failed to connect to serial port. " + "TS-4200 not functional until further notice. " + "Attempts to connect will continue indefinitely. " + "Error message: " + traceback.format_exc(Config.TRACEBACK_LIMIT), subject="TS-4200 Failed to Connect to Serial Port") errors.debug("Serial connection failed.") time.sleep( 5 ) #not much point in running the rest of the program without a serial connection, #so try until success #initialize various flags to keep track of errors: self.consecutiveEmptyStrings = 0 #number of strings consecutively not received from PIC self.totalEmptyStrings = 0 #total number of strings not received since start up or last reset self.PICStringIDErrorState = False #whether or not there is an outstanding unrecognized string coming from PIC self.PICFormatErrorState = False #whether or not there is an string of the wrong length coming from PIC #note that the above two will mask other errors of their sort until the first is fixed #(i.e., two unrecognized strings are sent repeatedly but only one is reported at first) self.totalBadStrings = 0 #number of received strings since start up or reset for which there was some error self.badFormatErrors = [ ] #hold every field/string requested by DataConverters that was not found self.dataStrings = {} #create empty dictionary to hold data open(Config.COMMAND_FILE_PATH, 'a').close() self.command_error = False errors.debug("DataReader initialization completed.")
def trigger(self,triggerer,objects): if self.triggerCheck(): self.timeLeft = self.time errors.debug(self.getID()+" triggered.") self.triggered = True
def save(self, header, toggle): """ Formats all received converted data and saves it to the appropriate configuration of files. Clears all data so outputter is available to receive a new batch. """ data_list = header #BUILD DATA STRING and check for missing data: for label in self.format: #for each expected piece of data try: data = str(self.data[label]) #request it from self.data except KeyError: #if it is not found, send an alert if label not in self.missing_data: #note that this alert applies only to data not sent at all -- data noted as missing earlier will errors.error( "DataOutputter missing data for label " + label + #have been replaced by an error value and so will not throw this error. ". Probable cause: no data converter passes data to outputter." + #this error should just catch coding mistakes (such as a piece of expected data not being passed to " Problem will continue until fixed." ) #the outputter for whatever reason) self.missing_data.append(label) data = str( Config.ERROR_VAL) #...and replace it with the error value data_list.append(data) #put each datum into a list data_string = "\t".join(data_list) + '\n' #turn the list into a string self.data = {} if (not os.path.exists(Config.CUR_BACKUP_PATH)): os.makedirs(Config.CUR_BACKUP_PATH) #clear data try: errors.debug("Attempting to write oneline") with open(Config.ONE_LINE_FILE_PATH, 'w') as one_line: #actually write data to file one_line.write(data_string) errors.debug("...write successful.") self.to_unix_error = False except: errors.debug("Copy failed.") if (toggle == 1): #WRITE TO 2UNIX: try: #this try block copies over backed up data to 2UNIX in case of interference or failure errors.debug("Copying temp_backup over to 2UNIX..." ) #this is a plan C, very unlikely to be needed. with open("temp_unix_backup", 'r') as backup: missing_data = backup.read() with open(Config.TO_UNIX_FILE_PATH, 'w') as to_unix: to_unix.write(missing_data) open("temp_unix_backup", "w").close() except: errors.debug("Copy failed.") try: errors.debug("Attempting to write to 2UNIX...") with open(Config.TO_UNIX_FILE_PATH, 'w') as to_unix: #actually write data to file to_unix.write(data_string) errors.debug("...write successful.") self.to_unix_error = False except IOError: time.sleep(.1) #if it fails, wait and try again errors.debug("...write failed, trying again.") try: with open(Config.TO_UNIX_FILE_PATH, 'w') as to_unix: #try to write again to_unix.write(data_string) errors.debug("Second write successful.") self.to_unix_error = False except IOError: #if that fails, put data in a backup errors.debug("Second write failed.") if not self.to_unix_error: errors.error( "Unable to write to file for database. Storing unsent data in a temporary backup." ) self.to_unix_error = True try: with open("temp_unix_backup", 'w') as backup: backup.write(data_string) errors.debug("Wrote data to backup.") self.temp_backup_error = False except: #if the backup fails, not much more that can be done if not self.temp_backup_error: errors.error( "Write to temporary backup failed. Data will not be written to 2UNIX." ) self.temp_backup_error = True else: errors.debug("Write to 2UNIX failed again.") if (toggle == 0): #WRITE TO 2UNIX: try: #this try block copies over backed up data to 2UNIX in case of interference or failure errors.debug("Copying temp_backup over to 2UNIX..." ) #this is a plan C, very unlikely to be needed. with open("temp_unix_backup", 'r') as backup: missing_data = backup.read() with open(Config.TO_UNIX_FILE_PATH, 'a') as to_unix: to_unix.write(missing_data) open("temp_unix_backup", "w").close() except: errors.debug("Copy failed.") try: errors.debug("Attempting to write to 2UNIX...") with open(Config.TO_UNIX_FILE_PATH, 'a') as to_unix: #actually write data to file to_unix.write(data_string) errors.debug("...write successful.") self.to_unix_error = False except IOError: time.sleep(.1) #if it fails, wait and try again errors.debug("...write failed, trying again.") try: with open(Config.TO_UNIX_FILE_PATH, 'a') as to_unix: #try to write again to_unix.write(data_string) errors.debug("Second write successful.") self.to_unix_error = False except IOError: #if that fails, put data in a backup errors.debug("Second write failed.") if not self.to_unix_error: errors.error( "Unable to write to file for database. Storing unsent data in a temporary backup." ) self.to_unix_error = True try: with open("temp_unix_backup", 'a') as backup: backup.write(data_string) errors.debug("Wrote data to backup.") self.temp_backup_error = False except: #if the backup fails, not much more that can be done if not self.temp_backup_error: errors.error( "Write to temporary backup failed. Data will not be written to 2UNIX." ) self.temp_backup_error = True else: errors.debug("Write to 2UNIX failed again.") #COPY FROM LOCAL TO FLASH DRIVE BACKUP (in case of flash drive coming back online) if os.path.ismount( Config.FLASH_BACKUP_DIREC_PATH ) and not self.flash_file_error: #checking whether drive is mounted so that code does not save to local file at same path try: errors.debug( "Copying local backup to flash." ) #try copying local backup to flash drive. if no data in local backup, just append nothing with open(Config.LOCAL_BACKUP_FILE_PATH, 'r') as local: #open and read data from local backup forFlash = local.read() with open(Config.TO_UNIX_BACKUP_PATH, 'a') as flash: #append data to flash backup flash.write(forFlash) open(Config.LOCAL_BACKUP_FILE_PATH, 'w').close() #clear local backup if successful self.flash_file_error = False except: if not self.flash_file_error: errors.info( "Copy from local to flash failed, but flash drive mounted. " + "Beware discontinuity in data starting around current time. " + "Error message: " + traceback.format_exc(Config.TRACEBACK_LIMIT)) os.system("sudo umount /dev/sda1") #unmount flash drive self.flash_file_error = True else: errors.debug( "Flash unmounted, copy from local to flash not attempted.") #BACKUP DATA ON FLASH DRIVE if os.path.ismount( Config.FLASH_BACKUP_DIREC_PATH): #if flash drive is mounted: try: #try writing data to backup on flash drive with open(Config.TO_UNIX_BACKUP_PATH, 'a') as flash: #open file and write data flash.write(data_string) if self.flash_drive_error: #if it didn't work last time, alert that it is working errors.error( "Write to flash drive successful -- flash backup online." ) self.flash_drive_error = False else: errors.debug( "Data written to flash drive successfully.") except: #if failure, alert as appropriate and write data to local backup instead if not self.flash_drive_error: errors.error( "Flash drive mounted, but unable to write to flash backup. " + "Error message: " + traceback.format_exc(Config.TRACEBACK_LIMIT)) self.flash_drive_error = True else: if not self.flash_drive_error: errors.error( "Flash drive unmounted. TS-4200 unable to write to backup file on flash drive. " + "Data diverted to local filesystem backup until notified otherwise." ) else: errors.debug( "Flash drive still unmounted, diverting data to local backup." ) self.flash_drive_error = True #IF FLASH DRIVE UNMOUNTED, WRITE TEMPORARILY TO LOCAL BACKUP try: with open(Config.LOCAL_BACKUP_FILE_PATH, 'a') as local: #open and write data to local backup local.write(data_string) except: errors.error( "BACKING UP FAILED: unable to write to either flash " + "or local backup. Error message: " + traceback.format_exc(Config.TRACEBACK_LIMIT)) self.data_list = data_list
def get(self, label): """ Given a label, returns that label's stored data as a string. If no such label is found in the given format or if the corresponding data point is not found in received data, registers an error message. Errors are stored in a list as the name (label, stringID, or ID+index) of the missing location. If a location is already in the list, email will not be resent. These errors are expected to handle cases of typos in picdata.conf or data converting code, missing strings, and so on. This method does not check type -- calling methods get either the raw data exactly as received (as a string) or Config.ERROR_VAL. Be sure NOT to use this method to request the name of a string (eg, B or T). """ try: tup = self.locations[ label] #try to retrieve information about the requested field from self.locations except KeyError: #if info retrieval throws error, notify... if label not in self.badFormatErrors: errors.error( "NO RAW DATA ERROR: data for '" + label + "' requested, but no such " + "label found. Either label not added to picdata.conf or misspelled " + "somewhere. Error message: " + traceback.format_exc(Config.TRACEBACK_LIMIT) + "Assume error continues until otherwise notified.") self.badFormatErrors.append( label) #...and add to list of errors... #errors.debug("get called but data for " + label + " not returned.") raise DataMissingError( label ) #...and return None, so calling method knows there is no data else: try: self.badFormatErrors.remove( label ) #if that succeeds, try to remove label from the list of missing data... errors.debug( "get called on " + label + " successful, removing error flag." ) #this little error sub-system is too subtle for comments -- see documentation except: pass #...but don't worry if it wasn't there in the first place try: #using the information from self.locations, stringID = tup[0] index = tup[1] data = self.dataStrings[stringID][ index] #try to find data for the requested label try: #if that succeeds, remove location from list of missing self.badFormatErrors.remove(stringID + str(index)) errors.debug("get called on " + stringID + str(index) + " successfully, removing error flag.") except: pass try: #if that succeeds, remove stringID from list of missing self.badFormatErrors.remove(stringID) errors.debug("get called on member of string " + stringID + " successfully, removing error flag.") except: pass except KeyError: #handle errors. these will only be called if there is a missing string or if stringID not in self.badFormatErrors: #some other corruption of data betweed the calling of read() and get() errors.error( "String '" + stringID + "' not found among received data: mismatch " + "between picdata.conf and requested data, probably. Error message: " + traceback.format_exc(Config.TRACEBACK_LIMIT) + " Assume error continues until " + "otherwise notified.") self.badFormatErrors.append(stringID) errors.debug("get called but data for " + label + " not returned.") raise DataMissingError(label) except IndexError: #this particular case should hardly ever be reached, but is here for completeness. if stringID + str(index) not in self.badFormatErrors: errors.error( "Data field " + stringID + str(index) + " not found among received data: " + "mismatch between picdata.conf and requested data. Error message: " + traceback.format_exc(Config.TRACEBACK_LIMIT) + " Assume error continues until " + "otherwise notified.") self.badFormatErrors.append(stringID + str(index)) errors.debug("get called but data for " + label + " not returned.") raise DataMissingError(label) return data #return data as a string. methods calling get() will be responsible for type-checking.
def process_all( ): #this function loops through all converters and calls safe_process on them. debug("process_all called; calling safe_process of all data converters...") for converter in all_converters: #it is called from main.py after data is read. converter.safe_process( ) #safe_process (defined in DataConverters.py) handles getting raw data from the reader, converting it, responding to
def loadXML(xmlPath,level,GameEngine,GraphicEngine): errors.info("Loading level: "+level) try: filer = open(xmlPath,"r") except IOError: errors.critical("Level file not found.") exit() lines = filer.readlines() started=False levelData = [] for i in range(len(lines)): #Strip Tabs and New Lines lines[i] = lines[i].lstrip("\t").rstrip("\n") for line in lines: #Extract Level Data if not started: if line == "<Level "+level+">": started=True continue if line == "</Level>": break levelData.append(line) Name=None BG = None Mask=None Enemies=None BattleBG=None BattleFBG=None Triggers = [] GameObjects = [] NPCs = [] i = 0 while i < len(levelData): #Isolate Triggers, NPCs, and GameObjects (GraphicObjects must be dealt with immediately because they are contained within GameObjects) temp = {} if levelData[i].startswith("<LevelName>"): Name=levelData[i].lstrip("<LevelName").lstrip(">").rstrip("/LevelName>").rstrip("<") elif levelData[i].startswith("<Background>"): BG=levelData[i].lstrip("<Background").lstrip(">").rstrip("/Background>").rstrip("<") elif levelData[i].startswith("<Mask>"): Mask=levelData[i].lstrip("<Mask").lstrip(">").rstrip("/Mask>").rstrip("<") elif levelData[i].startswith("<Enemies>"): Enemies = json.loads(levelData[i].lstrip("<Enemies").lstrip(">").rstrip("/Enemies>").rstrip("<")) elif levelData[i].startswith("<BattleBG>"): BattleBG = json.loads(levelData[i].lstrip("<BattleBG").lstrip(">").rstrip("/BattleBG>").rstrip("<")) elif levelData[i]=="<Trigger>": n=1 while levelData[i+n]!="</Trigger>": key = levelData[i+n][1:levelData[i+n].find(">")] value = levelData[i+n][levelData[i+n].find(">")+1:levelData[i+n].find("<",levelData[i+n].find(">"))] temp[key]=json.loads(value) n+=1 Triggers.append(temp) i+=n elif levelData[i].startswith("<GameObject"): path = levelData[i].lstrip("<GameObject").rstrip(">").lstrip(" ").split(" ")[0] if len(levelData[i].lstrip("<GameObject").rstrip(">").lstrip(" ").split(" "))>1: objectName = levelData[i].lstrip("<GameObject").rstrip(">").lstrip(" ").split(" ")[1] if path != "": tempFiler = file(config.AssetPath+path,"r") lines = tempFiler.readlines() started=False for j in range(len(lines)): #Strip Tabs and New Lines lines[j] = lines[j].lstrip("\t").rstrip("\n") j=1 for line in lines: #Extract Level Data if not started: if line == "<GameObject "+objectName+">": started=True continue if line == "</GameObject>": break levelData.insert(i+j,line) j+=1 #for line in levelData: # print line temp["graphicObject"] = {} n=1 while levelData[i+n]!="</GameObject>": key = levelData[i+n][1:levelData[i+n].find(">")] if key == "Mask": n+=1 temp["mask"] = {} while levelData[i+n]!="</Mask>": state = levelData[i+n][7:levelData[i+n].find(">")] temp["mask"][state] = json.loads(levelData[i+n][levelData[i+n].find(">")+1:levelData[i+n].rfind("<")]) n+=1 n+=1 elif key == "GraphicObject": if "animations" not in temp["graphicObject"]: temp["graphicObject"]["animations"] = {} n+=1 while levelData[i+n]!="</GraphicObject>": if levelData[i+n].startswith("<State"): state = levelData[i+n][7:levelData[i+n].find(">")] temp["graphicObject"]["animations"][state] = [None,None,None,None] n+=1 while levelData[i+n]!="</State>": #Retrieve Direction dire = int(levelData[i+n][11:levelData[i+n].find(">")]) xml = levelData[i+n][levelData[i+n].find(">")+1:levelData[i+n].rfind("<")] if dire == 0: temp["graphicObject"]["animations"][state][dire] = loadAnimation(xml,state+"N") elif dire == 1: temp["graphicObject"]["animations"][state][dire] = loadAnimation(xml,state+"E") elif dire == 2: temp["graphicObject"]["animations"][state][dire] = loadAnimation(xml,state+"S") elif dire == 3: temp["graphicObject"]["animations"][state][dire] = loadAnimation(xml,state+"W") n+=1 n+=1 else: key = levelData[i+n][levelData[i+n].find("<")+1:levelData[i+n].find(">")] value = levelData[i+n][levelData[i+n].find(">")+1:levelData[i+n].rfind("<")] temp["graphicObject"][key] = json.loads(value) n+=1 n+=1 else: value = levelData[i+n][levelData[i+n].find(">")+1:levelData[i+n].find("<",levelData[i+n].find(">"))] temp[key[0].lower()+key[1:]]=json.loads(value) n+=1 GameObjects.append(temp) i+=n elif levelData[i].startswith("<NPC"): path = levelData[i].lstrip("<NPC").rstrip(">").lstrip(" ").split(" ")[0] if len(levelData[i].lstrip("<NPC").rstrip(">").lstrip(" ").split(" "))>1: objectName = levelData[i].lstrip("<GameObject").rstrip(">").lstrip(" ").split(" ")[1] if path != "": tempFiler = file(config.AssetPath+path,"r") lines = tempFiler.readlines() started=False for j in range(len(lines)): #Strip Tabs and New Lines lines[j] = lines[j].lstrip("\t").rstrip("\n") j=1 for line in lines: #Extract Level Data if not started: if line == "<NPC "+objectName+">": started=True continue if line == "</NPC>": break levelData.insert(i+j,line) j+=1 n=1 while levelData[i+n]!="</NPC>": key = levelData[i+n][1:levelData[i+n].find(">")] value = levelData[i+n][levelData[i+n].find(">")+1:levelData[i+n].find("<",levelData[i+n].find(">"))] if key == "Dialog": if value != "null": dialog=open(config.AssetPath+value,"r") lines=dialog.readlines() dialog="" for line in lines: dialog+=line.rstrip("\n") dialog.replace("\t","") temp["Dialog"]=json.loads(dialog) else: temp["Dialog"]=None elif key == "AnimeXML": temp["AnimeXML"]=value elif key == "Icon": if value=="null": temp["Icon"]=None else: temp["Icon"]=value else: temp[key]=json.loads(value) n+=1 NPCs.append(temp) i+=1 i+=1 if Name == None: errors.warning("Level has no Name attribute.") Name = "Unknown Area" GraphicEngine.setLevelName(Name) if BG == None: errors.error("Level has no Background attribute.") else: try: GraphicEngine.setBackground(pygame.image.load(config.AssetPath+BG).convert()) except pygame.error: errors.error("Unable to load level background.") if Mask == None: errors.info("Level has no Mask attribute.") else: try: GameEngine.setMask(pygame.image.load(config.AssetPath+Mask).convert()) except pygame.error: errors.error("Unable to load level mask.") if Enemies != None and len(Enemies)>0: if BattleBG == None or len(BattleBG) == 0: errors.info("No battle backgrounds specified for this level.") else: # for i in xrange(len(BattleBG)): # for j in range(len(BattleBG[i])): # if BattleBG[i][j] != None: # try: # BattleBG[i][j] = pygame.image.load(BattleBG[i][j]).convert_alpha() # except pygame.error: # errors.error("Unable to load battle background for this level.") # BattleBG[i][j] = None GameEngine.setBattleBG(BattleBG) GameEngine.setEnemies(Enemies) else: GameEngine.setBattleBG([]) GameEngine.setEnemies([]) for trigger in Triggers: errors.debug("Adding "+trigger["Id"]+" trigger.") # Should probably be using a factory... if "Area" in trigger.keys() and trigger["Area"]!=None: trigger["Area"] = pygame.rect.Rect(trigger["Area"]) if trigger["Effect"]=="State Set": del trigger["Effect"] GameEngine.addTrigger(triggers.StateSetTrigger(**trigger)) elif trigger["Effect"]=="State Toggle": del trigger["Effect"] GameEngine.addTrigger(triggers.StateToggleTrigger(**trigger)) elif trigger["Effect"]=="Area Change": del trigger["Effect"] GameEngine.addTrigger(triggers.AreaChangeTrigger(**trigger)) elif trigger["Effect"]=="SBSC": del trigger["Effect"] GameEngine.addTrigger(triggers.SBSCTrigger(**trigger)) elif trigger["Effect"]=="TBSC": del trigger["Effect"] GameEngine.addTrigger(triggers.TBSCTrigger(**trigger)) elif trigger["Effect"]=="Battle": del trigger["Effect"] GameEngine.addTrigger(triggers.BattleTrigger(**trigger)) elif trigger["Effect"]=="Item": del trigger["Effect"] GameEngine.addTrigger(triggers.ItemTrigger(**trigger)) elif trigger["Effect"]=="Quest Complete": del trigger["Effect"] GameEngine.addTrigger(triggers.QuestCompleteTrigger(**trigger)) else: errors.error("Undefined Trigger Effect") for obj in GameObjects: errors.debug("Adding "+obj["id"]) #print str(obj["id"])+":" #for key in obj.keys(): # print key,obj[key] #for key in obj["graphicObject"].keys(): # print key,obj["graphicObject"][key] #for key in obj["graphicObject"]["animations"].keys(): # for i in range(0,4): # if obj["graphicObject"]["animations"][key][i] != None: # print key+str(i),len(obj["graphicObject"]["animations"][key][i].getFrames()) # else: # print key+str(i),None #print "\n" for state in obj["mask"].keys(): if obj["mask"][state] != None: img = pygame.image.load(config.AssetPath+str(obj["mask"][state])) obj["mask"][state] = maskFromSurface(img) if obj["graphicObject"].keys().__contains__("flipX"): for state in obj["graphicObject"]["animations"].keys(): i=0 if obj["graphicObject"]["animations"][state][1].getNextAnimation() != None: nextAnimation = obj["graphicObject"]["animations"][state][1].getNextAnimation()[:-1]+"W" else: nextAnimation = None obj["graphicObject"]["animations"][state][3] = Animation(None,nextAnimation,state+"W") for frame in obj["graphicObject"]["animations"][state][1].getFrames(): i+=1 obj["graphicObject"]["animations"][state][3].addFrame(AnimationFrame(pygame.transform.flip(frame.image,True,False),frame.delay,None,i-1)) del obj["graphicObject"]["flipX"] #Animation Linker: for state in obj["graphicObject"]["animations"].keys(): for dire in range(0,4): if obj["graphicObject"]["animations"][state][dire] == None: continue nextState = obj["graphicObject"]["animations"][state][dire].getNextAnimation() if nextState == None or type(nextState)==Animation: continue if obj["graphicObject"]["animations"][nextState.rstrip("NESW")][dire].getName()==nextState: obj["graphicObject"]["animations"][state][dire].nextAnimation = obj["graphicObject"]["animations"][nextState.rstrip("NESW")][dire] else: errors.error("Animation linker is officially insufficient. \n(It was already unofficially insufficient, but now things just got worse)\n((Troublemaker: "+state+" -> "+nextState+"))") obj["graphicObject"] = GraphicObject(**obj["graphicObject"]) GraphicEngine.addObject(obj["graphicObject"]) if obj.keys().__contains__("pushable") and obj["pushable"]==True: del obj["pushable"] if obj["area"]!=None: obj["area"] = pygame.rect.Rect(obj["area"]) GameEngine.addActor(Pushable(**obj)) else: GameEngine.addActor(GameObject(**obj)) for npc in NPCs: if "AnimeXML" in npc: errors.debug("Adding sNPC: "+npc["Id"]) if npc["Icon"]!=None: npc["Icon"]=pygame.image.load(npc["Icon"]).convert() npc["Dialog"]=loadDialog(npc["Dialog"]) #print npc["Dialog"] temp = sNPC(**npc) GameEngine.addNPC(temp) GraphicEngine.addObject(temp.getGraphicObject()) else: errors.debug("Adding NPC: "+npc["Id"]) if npc["Icon"]!=None: npc["Icon"]=pygame.image.load(npc["Icon"]).convert() npc["Dialog"]=loadDialog(npc["Dialog"]) #print npc["Dialog"] temp = NPC(**npc) GameEngine.addNPC(temp) GraphicEngine.addObject(temp.getGraphicObject()) filer.close()
except IndexError: if not self.for_dash_error: errors.error( "Error in parsing dashformat.txt; file may have been " + "corrupted. Dashboard may not display data properly or correctly." + " Error message: " + traceback.format_exc(Config.TRACEBACK_LIMIT)) else: errors.debug( "Error in writing data_for_dash again, don't trust dashboard." ) self.for_dash_error = True self.data_list = [] if __name__ == '__main__': george = DataOutputter() for i in range(20): errors.debug("===============NEW CYCLE==============") george.receive("mux_current", i) george.save() errors.sendEmail() time.sleep(15) george.reset() for i in range(20): errors.debug("===============NEW CYCLE==============") george.receive("mux_current", i) george.save() errors.sendEmail() time.sleep(15)