def os_open_logfile( self, ): """ py_log file is opened in the configured system editor for the user to view and edit may be used as/by callback from gui button. Can be called form gt """ # a_filename = self.parameters.py_path + os.path.sep + self.parameters.pylogging_fn AppGlobal.os_open_txt_file( self.parameters.pylogging_fn )
def config_logger( self, ): """ configure the logger in usual way using the current parameters ?? move to app global or in that direction args: zip ret: the logger and side effects including adding logger to AppGlobal """ logger = logging.getLogger( self.logger_id ) logger.handlers = [] logger.setLevel( self.parameters.logging_level ) # DEBUG , INFO WARNING ERROR CRITICAL # create the logging file handler..... fh = logging.FileHandler( self.parameters.pylogging_fn ) formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') fh.setFormatter( formatter ) logger.addHandler( fh ) msg = "Done config_logger" #rint( msg ) logger.debug( msg ) # .debug .info .warn .error AppGlobal.set_logger( logger ) return logger
def write_header(self, ): """ what is says Args: Return: state change, output Raises: none planned, file open could fail """ msg = "write header...." AppGlobal.print_debug( msg ) self.open_output_file() #self.fileout = open( self.file_name, "w", encoding = "utf8", errors = 'replace' ) columns_info = self.builder.columns_info # believe it is also a dict like a data dict columns_out = self.builder.columns_out line_parts = [] for ix, i_col in enumerate( columns_out ): i_col_info = columns_info[i_col] fmt = i_col_info["text_format"] col_text = i_col_info["column_head"] line_parts.append( fmt.format( x = col_text ) ) line = "\t".join( line_parts ) #rint( line, flush = True ) self.fileout.write( line + "\n" )
def start_graph_live(self, a_ax): """ main thread """ # print( f"start_graph_live in adapter {self.name} at {self.tcpip}" ) # msg = f"? ? start_graph_live for device: {self.name} " # AppGlobal.what_thread( threading.get_ident(), msg, 20 ) msg = "SmartPlugAdapter.start_graph_live " AppGlobal.log_if_wrong_thread(threading.get_ident(), msg=msg, main=True) if not self.monitor: return if self.gd_time_adj is None: self.gd_time_adj = [] self.gd_power = [] #self.reset_graphing_data() # why bother consider keeping data !! #if self.live_graph_lines is None: # or use self.live_graph_ready if self.live_graph_ready == False: # or use self.live_graph_ready self.live_graph_lines, = a_ax.plot( [], [], 'o', label=self.name ) # note unpack comma -- seem to be a list of 1 element # msg = f"self.live_graph_lines created for device: {self.name} {type(self.live_graph_lines)} {self.live_graph_lines}" # AppGlobal.logger.info( msg ) # print( msg ) # got >>>> why a list self.live_graph_lines: <class 'list'> [<matplotlib.lines.Line2D object at 0x00000274E0287AC8>] self.linestyle, self.colorstyle, self.markerstyle, self.widthstyle = AppGlobal.graph_live.line_style.get_next_style( ) self.live_graph_ready = True
def check_timer_done(self, ): """ for now do not assume timer is running call from ht polling action """ msg = "SmartPlugAdapter.check_timer_done " AppGlobal.log_if_wrong_thread(threading.get_ident(), msg=msg, main=False) if not (self.timer_on): return now = time.time() sec_left = (self.timer_start + self.time_sec) - now # could be timer_end stored # print( f"check_timer_done sec_left {sec_left} for plug: {self.name} " ) # debug if sec_left <= 0: self.off() self.display_msg("Timer Stop") else: self.timer_sec_left = sec_left a_string = self.sec_to_string(sec_left) # just update display self.display_msg(f"Timer {a_string}")
def update_graph_live(self, ): """ call from main thread from graph_live but could we use our own polling ?? return True or False, depending on weather we were already set up """ msg = "SmartPlugAdapter.update_graph_live " AppGlobal.log_if_wrong_thread(threading.get_ident(), msg=msg, main=True) # msg = f"Adapter.update_graph_live device: {self.name} data: {self.graphing_new_data} ready: {self.live_graph_ready}" # AppGlobal.logger.info( msg ) if (not self.graphing_new_data) or (not self.live_graph_ready): return False self.live_graph_lines.set_xdata(self.gd_time_adj) self.live_graph_lines.set_ydata(self.gd_power) # not sure these need to be done on every cycle but works self.live_graph_lines.set_linestyle(self.linestyle) self.live_graph_lines.set_color(self.colorstyle) self.live_graph_lines.set_marker(self.markerstyle) self.live_graph_lines.set_linewidth(self.widthstyle) self.live_graph_lines.set_label( self.name ) # not currently working lacks update or.... setting legend later seemed to do it once may be enough self.graphing_new_data = False return True
def __init__(self, ): AppGlobal.parameters = self self.default_mode() self.running_on_tweaks() self.choose_mode() AppGlobal.parameter_tweaks() # including restart
def os_open_helpfile( self, ): """ callback from gui button """ help_file = self.parameters.help_file AppGlobal.os_open_help_file( help_file )
def check_recording(self, ): """ call helper thread, best or ok in main -- used by smart_plug not smart_plug_graphing if recording then record the data in db for now do not assume timer is running call from ht polling action """ msg = "SmartPlugAdapter.check_recording " AppGlobal.log_if_wrong_thread(threading.get_ident(), msg=msg, main=False) if not (self.recording or self.monitor): return # msg = f"?ht? adapter check_recording " # AppGlobal.what_thread( threading.get_ident(), msg, 20 ) now = time.time() if now < self.next_record_time: # or monitor time return self.last_record_time = now self.next_record_time = now + self.delta_t try: plug = pyHS100.SmartPlug(self.tcpip) plug_data = plug.get_emeter_realtime() # ?? could add plug state - on off,..... # id = f"{self.name}{self.last_record_time}r" # print ( f"record data {id}:{plug_data})" ) rnd = "%.2f" % plug_data["power"] msg = f'{rnd} watts' #!! bit of rounding would be nice here self.gui_tk_label_2.config(text=msg) except pyHS100.smartdevice.SmartDeviceException as exception: # look up correct exception self.timer_on = False #self.record_off() to much messaging and will throw own error msg = f"failed to communicate with plug - record off for: {self.name} {self.tcpip}" AppGlobal.gui.print_info_string(msg) return if AppGlobal.graph_live_flag and self.monitor and self.live_graph_ready: # print( "check_recording" ) self.graphing_new_data = True #!! normalized and unnormalize data self.gd_time.append(self.last_record_time) self.gd_time_adj.append( AppGlobal.graph_live.rescale_time_function( self.last_record_time)) #?? make local ref?? self.gd_power.append(plug_data["power"]) if self.recording: db_data = ((self.name, self.last_record_time, "r", "?", plug_data["voltage"], plug_data["current"], plug_data["power"], plug_data["total"]), ) self.insert_measurements(db_data)
def os_open_helpfile( self, ): """ help "file" is opened in the os configured application for the file type: file set in parameters may be txt, pdf, or a url method may be used as callback from gui button """ help_file = self.parameters.help_file AppGlobal.os_open_help_file( help_file )
def os_open_parmfile( self, ): """ parameter file is opened in the configured system editor for the user to view and edit may be used as callback from gui button path addition here because parameters.py is not itself a parameter """ a_filename = self.parameters.py_path + os.path.sep + "parameters.py" AppGlobal.os_open_txt_file( a_filename )
def polling_mt(self, ): """ polling for the main thread ( gui thread ) """ msg = "SmartPlugAdapter.polling_mt " AppGlobal.log_if_wrong_thread(threading.get_ident(), msg=msg, main=True)
def polling_ht(self, ): """ polling for the helper thread not sure this is ever needed """ msg = "GraphLive.polling_ht " AppGlobal.log_if_wrong_thread(threading.get_ident(), msg=msg, main=False)
def polling(self): """ started from gui thread as beginning of new thread this is an infinite loop monitoring the queue actions based on queue_to_helper and run_event application purpose is the device polling where we monitor/record the devices """ #self.logger.debug( "HealperThread.polling() entered " ) msg = "smart plug Helper.polling()" AppGlobal.log_if_wrong_thread(threading.get_ident(), msg=msg, main=False) while True: try: # if self.last_time + 10 < time.time(): # self.last_time = time.time() # self.print_info_string( "Time" + str( self.last_time ) ) # + a_port ) (action, function, function_args) = self.rec_from_queue() if action != "": self.logger.debug("smart_plug_helper.polling() queue: " + action + " " + str(function) + " " + str(function_args)) # ?? comment out if action == "call": #print( "ht making call" ) sys.stdout.flush() self.controller.helper_task_active = True function(*function_args) self.logger.debug( "smart_plug_helper.polling() return running helper loop " ) # ?? comment out #self.print_helper_label( "return running helper loop " ) self.controller.helper_task_active = False # do we maintain this, or move to helper -- looks like not used, app global better location if action == "stop": # seems to be working fine msg = "helper got stop in the queue -- end thread with return " # AppGlobal.what_thread( threading.get_ident(), msg, 50 ) self.controller.helper_task_active = False return # this will kill the thread --- think it is the return which should end the loop ? or do we need a break -- we seem to keep looping msg = "helper got stop in the queue -- ran thru return " # AppGlobal.what_thread( threading.get_ident(), msg, 50 ) self.device_polling() # must catch all exceptions if we do not want polling to stop ... but maybe we do except Exception as he: #self.logger.info( "schedule_me_helper.HelperThread threw exception from " + he.msg ) # info debug... !! also info to msg area msg = f"smart_plug_helper.HelperThread threw exception: {he}" print(f"see log: {msg}") self.logger.error(msg, exc_info=True) # info debug... time.sleep( self.ht_delta_t) # ok here since it is the main pooling loop #self.logger.debug( "HealperThread.polling() done with sleep " ) return
def polling_ht(self, ): """ polling for the helper thread """ msg = "SmartPlugAdapter.polling_ht " AppGlobal.log_if_wrong_thread(threading.get_ident(), msg=msg, main=False) self.check_timer_done() self.check_recording()
def reset_graphing_data(self, ): """ fine in helper thread ok in both """ AppGlobal.log_if_wrong_thread( threading.get_ident(), msg="now in smart_plug_adapter.reset_graphing_data", main=True) # print( "reset_graphing_data" ) self.gd_time = [] self.gd_time_adj = [] self.gd_power = [] #self.gd_power_adj = [] self.gd_energy = [] self.gd_energy_adj = [] self.graphing_new_data = False
def run(self): """ called when thread is started from gt its call should be considered as from ht """ self.parameters.init_from_helper() self.scheduled_event_list = AppGlobal.scheduled_event_list id = threading.get_ident() msg = f"helper run " AppGlobal.log_if_wrong_thread(threading.get_ident(), msg=msg, main=False) self.logger.debug(msg) # print( "==============================================" ) self.polling() # may mach name in gui thread
def graph_power_from_db(self, line_style=None, ax=None): """ None default because required hence exception """ msg = "SmartPlugAdapter.graph_power_from_db " AppGlobal.log_if_wrong_thread(threading.get_ident(), msg=msg, main=True) line_style.get_next_style() time_data = self.gd_time_adj if ((time_data is None) or len(time_data) == 0): print(f"no data for {self.name}") return ax.plot(time_data, self.gd_power, linestyle=line_style.linestyle, marker=line_style.markerstyle, color=line_style.colorstyle, label=self.name) # label= "Power (Watts)"
def write_header(self, ): """ what is says Args: Return: state change, output Raises: none planned """ lines = [] # will use in other methods self.col_names = [ i_format[0] for i_format in self.table_info.format_list ] msg = f"write_header() self.col_names {self.col_names}" AppGlobal.print_debug( msg ) i_line = f"#---------- SelectLogWriter output from {AppGlobal.controller.app_name} {AppGlobal.controller.app_version}" lines.append( i_line ) lines.append( f" self.table_info.sql = {self.table_info.sql}" ) i_line = f"use_table:{self.table_info.table_name}" lines.append( i_line ) msg = "\n".join( lines ) AppGlobal.logger.log( AppGlobal.force_log_level, msg )
def device_polling(self): """ helper thread poll the devices - now just smartplugs, but fairly easily adapted to other polling tasks. so simple may put back in polling also extended for graphing """ # print( "device_polling" ) msg = "HealperThread.device_polling " AppGlobal.log_if_wrong_thread(threading.get_ident(), msg=msg, main=False) # AppGlobal.what_thread( threading.get_ident(), msg, 50 ) now = time.time() for i_adapter in AppGlobal.smartplug_adapter_list: i_adapter.polling_ht() # now the live graph # print( f"AppGlobal.graph_live {AppGlobal.graph_live_flag}") if not AppGlobal.graph_live_flag: return # print( "smart_plug_helper device_polling graph_live" ) # think this test now in graph_live update_graph_bool = False for i_adapter in AppGlobal.smartplug_adapter_list: if i_adapter.graphing_new_data: update_graph_bool = True break # print( f"update_graph_bool {update_graph_bool}" ) if not update_graph_bool: # print( f"update_graph_bool {update_graph_bool}" ) return AppGlobal.logger.error( "SmartPlugHelper.device_polling off to graph live") AppGlobal.graph_live.polling_ht()
def polling_mt(self, ): """ polling for the main thread ( gui thread ) """ msg = "GraphLive.polling_mt " AppGlobal.log_if_wrong_thread(threading.get_ident(), msg=msg, main=True) # AppGlobal.logger.error( msg ) # collect flags from the individual devices need_redraw_flag = False for i_adapter in AppGlobal.smartplug_adapter_list: if i_adapter.update_graph_live(): need_redraw_flag = True #AppGlobal.logger.error( "GraphLive.polling_mt test next" ) if need_redraw_flag: AppGlobal.logger.error("GraphLive.polling_mt need redraw True") self.ax.relim() self.ax.autoscale_view() #We need to draw *and* flush AppGlobal.logger.error("GraphLive.polling_mt next draw flush") self.figure.canvas.draw() self.figure.canvas.flush_events() plt.legend(loc=2) # help with update -- seems to work # AppGlobal.logger.error( "GraphLive.polling_mt polling show next" ) plt.show(block=False) # AppGlobal.logger.error( "GraphLive.polling_mtpolling post next" ) # ------------ for tracking memory use self.debug_count += 1 if self.debug_count > 50: AppGlobal.show_process_memory( "GraphLive.polling_mt polling time to log", log_level=20) self.debug_count = 0
def add_data_path( file_name ): """ complete short path names by adding data_dir part ... remove instance function of same name from another app may not be used here """ ret = "" # why, will fail unless changed, just not well though out try: # in case of config errors log ret = os.path.join( AppGlobal.parameters.data_dir, file_name ) except Exception as exception: # should not really catch all AppGlobal.print_debug( exception.msg ) AppGlobal.gui_write_error( exception.msg ) #self.os_open_text_file( self.parameters.pylogging_fn ) msg = "Exception building file name {AppGlobal.parameters.data_dir} and {file_name} see py_log" #AppGlobal.print_debug( msg ) AppGlobal.gui_write_error( msg ) return ret
def os_open_parmxfile( self, ): """ used as callback from gui button """ a_filename = self.starting_dir + os.path.sep + self.parmeters_x + ".py" AppGlobal.os_open_txt_file( a_filename )
def os_open_parmfile( self, ): """ callback from gui button """ a_filename = self.starting_dir + os.path.sep + "parameters.py" AppGlobal.os_open_txt_file( a_filename )
def os_open_logfile( self, ): """ callback from gui button """ AppGlobal.os_open_txt_file( self.parameters.pylogging_fn )
def os_open_parmfile( self, ): """ used as callback from gui button -- rename cb ?? """ a_filename = self.starting_dir + os.path.sep + "parameters.py" # assuming a txt file AppGlobal.os_open_txt_file( a_filename )
def make_file_writer( builder, ): """ Purpose: what it says -- make a file_writer of the correct format output_format Args: builder -- among other things uses output_format to determine type of writer and file name Returns: a fileWriter, mutates builder Issues: could use a thoughtful refactoring --- consider dict version """ output_format = builder.output_format # msg = f"make_file_writer: for format {output_format}" #rint( msg ) # some are left over and need to be changed # AppGlobal.print_debug( msg ) # AppGlobal.gui_write_progress( msg ) if output_format == "py_log": # fileout_name = add_data_path( AppGlobal.parameters.pylogging_fn ) builder.output_name = "not used" select_writer = SelectLogWriter( builder ) # our use os.path.join( rpath, "temp", "zxqq.txt" ) )) elif output_format == "csv": # fileout_name = add_data_path( "output_select.csv" ) builder.output_name = AppGlobal.parameters.output_path + f"{os.sep}output_select.csv" #AppGlobal.parameters.output_path select_writer = SelectCSVWriter( builder ) elif output_format == "txt": builder.output_name = AppGlobal.parameters.output_path + f"{os.sep}output_select.txt" select_writer = SelectTxtWriter( builder ) elif output_format == "yaml": # still need to do builder.output_name = AppGlobal.parameters.output_path + f"{os.sep}output_select.yaml" select_writer = SelectYamlWriter( builder ) elif output_format == "html": #fileout_name = add_data_path( "output_select.html" ) builder.output_name = AppGlobal.parameters.output_path + f"{os.sep}output_select.html" select_writer = SelectHTMLWriter( builder ) elif output_format == "msg": #fileout_name = add_data_path( "output_select.html" ) builder.output_name = AppGlobal.parameters.output_path + f"{os.sep}output_select.msg" # !! why 2 was one for test resolve and fix select_writer = SelectMessageWriter( builder ) print( "select_writer = SelectMsgWriter( builder )" ) select_writer = SelectMsgWriter( builder ) # elif output_format == "zap": # #fileout_name = add_data_path( "output_select.html" ) # builder.output_name = AppGlobal.parameters.output_path + f"{os.sep}output_select.msg" # select_writer = SelectZapWriter( builder ) else: msg = f"invalid output_format = {output_format}" AppGlobal.gui.display_info_string( msg ) AppGlobal.print_debug( msg ) raise Exception( msg ) # msg = f"make_file_writer: return {( select_writer, fileout_name )}" # AppGlobal.print_debug( msg ) return ( select_writer )
def os_open_logfile(self, ): """ used as/by callback from gui button. Can be called form gt """ AppGlobal.os_open_txt_file(self.parameters.pylogging_fn)
def cb_about( self, ): """ call back for gui button """ AppGlobal.about()
def start_graph_live(self, ): """ call from main thread start off a session of live graphing -- need to run in main thread moved from the plug no validation if start is ok no inactivation of controls .... even this should perhaps come from a checkbox what do we want to do: graph only plugs that are being monitored or recorded accumulate the data in a list ( list of tuples might be better than two lists or even a numpy type thing ) ??? what is monitor, record is turned off ( keep data and graph line so far stop adding to data ) ??? what if new adapter is turned on ( was it on earlire ?? ) ??? export csv export all cached data to a csv, if none just a message """ msg = "GraphLive.start_graph_live" # AppGlobal.what_thread( threading.get_ident(), msg, 20 ) AppGlobal.log_if_wrong_thread(threading.get_ident(), msg=msg, main=True) self.rescale_time_function = lambda x: x # default identity function self.set_rescale_time_function() #Set up plot self.figure, self.ax = plt.subplots( figsize=(self.parameters.graph_x_size, self.parameters.graph_y_size)) self.figure.canvas.mpl_connect('close_event', self.end_graph_live_evt) plt.legend( loc=2 ) # for label to show up ? # made small sq show up no content, too early ? # lets try to build the line in each adapter self.lines, = self.ax.plot( [], [], 'o', color="red", linewidth=2.5, linestyle="-" ) # the line is the thing to update ... can we add later, lets assume we can #Auto scale on unknown axis and known limits on the other self.ax.set_autoscaley_on(True) #self.ax.set_xlim(self.min_x, self.max_x) #Other stuff self.ax.grid(linestyle='-', linewidth='0.5', color='red') self.ax.tick_params(axis='y', labelcolor='red') self.ax.set_title(f"Power measured by SmartPlugs") self.ax.set_xlabel( f"Time in {self.graph_time_units} from {self.graph_time_zero}" ) # these have been set multiple times self.ax.set_ylabel( f"Power in watts") # these have been set multiple times for i_adapter in AppGlobal.smartplug_adapter_list: i_adapter.start_graph_live( self.ax ) # this is a setup it does not start graphing by itself plt.show(block=False) AppGlobal.logger.error("GraphLive.start_graph_live complete") AppGlobal.graph_live_flag = True # this will kick off in polling set last