def __init__(self): # utils, debug=False, log=None, log_file=None): self.go = True self.states = { True: '{{green}}ON{{norm}}', False: '{{red}}OFF{{norm}}' } self.ignored_errors = [] # Populated by menu script consolepi-menu.py self.log_sym_2bang = '\033[1;33m!!\033[0m' if sys.stdin.isatty(): self.rows, self.cols = utils.get_tty_size() self.menu_rows = 0 # Updated in menu_formatting self.menu_cols = 0
def menu_exec(self, choice, menu_actions, calling_menu="main_menu"): '''Execute Menu Selection. This method needs to be overhauled. The ConsolePiAction object defined but not used in __init__ is part of the plan for overhaul menu will build insance of the object for each selection. That will be used to determine what action to perform and what to do after etc. ''' pwr = self.pwr if not config.debug and calling_menu not in ["dli_menu", "power_menu"]: os.system("clear") if ( not choice.lower or choice.lower in menu_actions and menu_actions[choice.lower] is None ): ( self.menu.rows, self.menu.cols, ) = ( utils.get_tty_size() ) # re-calc tty size in case they've adjusted the window return else: ch = choice.lower try: # Invalid Selection if isinstance(menu_actions[ch], dict): if menu_actions[ch].get("cmd"): # TimeStamp for picocom session log file if defined menu_actions[ch]["cmd"] = menu_actions[ch]["cmd"].replace( "{{timestamp}}", time.strftime("%F_%H.%M") ) # -- // AUTO POWER ON LINKED OUTLETS \\ -- if ( config.power and "pwr_key" in menu_actions[ch] ): self.exec_auto_pwron(menu_actions[ch]["pwr_key"]) # -- // Print pre-connect messsge if provided \\ -- if menu_actions[ch].get("pre_msg"): print(menu_actions[ch]["pre_msg"]) # --// execute the command \\-- try: _error = None if "exec_kwargs" in menu_actions[ch]: c = menu_actions[ch]["cmd"] _error = utils.do_shell_cmd( c, **menu_actions[ch]["exec_kwargs"] ) if _error and self.autopwr_wait: # TODO simplify this after moving to action object _method = "ssh -t" if "ssh" in c else "telnet" if "ssh" in _method: _h = ( c.split(f"{_method} ")[1] .split(" -p")[0] .split("@")[1] ) _p = int(c.split("-p ")[1]) elif _method == "telnet": c_list = c.split() _h = c_list[-2] _p = int(c_list[-1]) def wait_for_boot(): while True: try: if utils.is_reachable(_h, _p, silent=True): break else: time.sleep(3) except KeyboardInterrupt: self.autopwr_wait = False log.show("Connection Aborted") break print( "\nInitial Attempt Failed, but host is linked to an outlet that was" ) print("off. Host may still be booting\n") utils.spinner( f"Waiting for {_h} to boot, CTRL-C to Abort", wait_for_boot, ) if self.autopwr_wait: _error = utils.do_shell_cmd(c, **menu_actions[ch]["exec_kwargs"]) self.autopwr_wait = False else: c = shlex.split(menu_actions[ch]["cmd"]) result = subprocess.run(c, stderr=subprocess.PIPE) _stderr = result.stderr.decode("UTF-8") if _stderr or result.returncode == 1: _error = utils.error_handler(c, _stderr) if _error: log.show(_error) # -- // resize the terminal to handle serial connections that jack the terminal size \\ -- c = " ".join([str(i) for i in c]) if "picocom" in c: os.system( "/etc/ConsolePi/src/consolepi-commands/resize >/dev/null" ) except KeyboardInterrupt: log.show("Aborted last command based on user input") elif "function" in menu_actions[ch]: args = ( menu_actions[ch]["args"] if "args" in menu_actions[ch] else [] ) kwargs = ( menu_actions[ch]["kwargs"] if "kwargs" in menu_actions[ch] else {} ) confirmed, spin_text, name = self.confirm_and_spin( menu_actions[ch], *args, **kwargs ) if confirmed: # update kwargs with name from confirm_and_spin method if menu_actions[ch]["function"].__name__ == "pwr_rename": kwargs["name"] = name # // -- CALL THE FUNCTION \\-- if ( spin_text ): # start spinner if spin_text set by confirm_and_spin with Halo(text=spin_text, spinner="dots2"): response = menu_actions[ch]["function"]( *args, **kwargs ) else: # no spinner response = menu_actions[ch]["function"](*args, **kwargs) # --// Power Menus \\-- if calling_menu in ["power_menu", "dli_menu"]: if menu_actions[ch]["function"].__name__ == "pwr_all": with Halo( text="Refreshing Outlet States", spinner="dots" ): self.outlet_update( refresh=True, upd_linked=True ) # TODO can I move this to Outlets Class else: _grp = menu_actions[ch]["key"] _type = menu_actions[ch]["args"][0] _addr = menu_actions[ch]["args"][1] # --// EVAL responses for dli outlets \\-- if _type == "dli": host_short = utils.get_host_short(_addr) _port = menu_actions[ch]["kwargs"]["port"] # --// Operations performed on ALL outlets \\-- if ( isinstance(response, bool) and _port is not None ): if ( menu_actions[ch]["function"].__name__ == "pwr_toggle" ): self.spin.start( "Request Sent, Refreshing Outlet States" ) # threading.Thread(target=self.get_dli_outlets, # kwargs={'upd_linked': True, 'refresh': True}, name='pwr_toggle_refresh').start() upd_linked = ( True if calling_menu == "power_menu" else False ) # else dli_menu threading.Thread( target=self.outlet_update, kwargs={ "upd_linked": upd_linked, "refresh": True, }, name="pwr_toggle_refresh", ).start() if _grp in pwr.data["defined"]: pwr.data["defined"][_grp]["is_on"][ _port ]["state"] = response elif _port != "all": pwr.data["dli_power"][_addr][_port][ "state" ] = response else: # dli toggle all for t in threading.enumerate(): if ( t.name == "pwr_toggle_refresh" ): t.join() # if refresh thread is running join ~ # wait for it to complete. # TODO Don't think this works or below # wouldn't have been necessary. # toggle all returns True (ON) or False (OFF) if command # successfully sent. In reality the ports # may not be in the state yet, but dli is working it. # Update menu items to reflect end state for p in pwr.data[ "dli_power" ][_addr]: pwr.data["dli_power"][ _addr ][p]["state"] = response break self.spin.stop() # Cycle operation returns False if outlet is off, only valid on powered outlets elif ( menu_actions[ch]["function"].__name__ == "pwr_cycle" and not response ): log.show( f"{host_short} Port {_port} is Off. Cycle is not valid" ) elif ( menu_actions[ch]["function"].__name__ == "pwr_rename" ): if response: _name = pwr._dli[_addr].name(_port) if _grp in pwr.data.get( "defined", {} ): pwr.data["defined"][_grp][ "is_on" ][_port]["name"] = _name else: threading.Thread( target=self.outlet_update, kwargs={ "upd_linked": True, "refresh": True, }, name="pwr_rename_refresh", ).start() pwr.data["dli_power"][_addr][_port][ "name" ] = _name # --// str responses are errors append to error_msgs \\-- # TODO refactor response to use new cpi.response(...) elif ( isinstance(response, str) and _port is not None ): log.show(response) # --// Can Remove After Refactoring all responses to bool or str \\-- elif isinstance(response, int): if ( menu_actions[ch]["function"].__name__ == "pwr_cycle" and _port == "all" ): if response != 200: log.show( "Error Response Returned {}".format( response ) ) # This is a catch as for the most part I've tried to refactor so the pwr library # returns port state on success (True/False) else: if response in [200, 204]: log.show( "DEV NOTE: check pwr library ret=200 or 204" ) else: _action = menu_actions[ch][ "function" ].__name__ log.show( f"Error returned from dli {host_short} when " f"attempting to {_action} port {_port}" ) # --// EVAL responses for espHome outlets \\-- elif _type == "esphome": host_short = utils.get_host_short(_addr) _port = menu_actions[ch]["kwargs"]["port"] # --// Operations performed on ALL outlets \\-- if (isinstance(response, bool) and _port is not None): pwr.data['defined'][_grp]['is_on'][_port]['state'] = response if ( menu_actions[ch]["function"].__name__ == "pwr_cycle" and not response ): _msg = f"{_grp}({host_short})" if _grp != host_short else f"{_grp}" if _msg != _port: _msg = f"{_msg} Port {_port} is Off. Cycle is not valid" else: _msg = f"{_msg} is Off. Cycle is not valid" log.show(_msg) elif (isinstance(response, str) and _port is not None): log.show(response) # --// EVAL responses for GPIO and tasmota outlets \\-- else: if ( menu_actions[ch]["function"].__name__ == "pwr_toggle" ): if _grp in pwr.data.get("defined", {}): if isinstance(response, bool): pwr.data["defined"][_grp][ "is_on" ] = response else: pwr.data["defined"][_grp][ "errors" ] = response elif ( menu_actions[ch]["function"].__name__ == "pwr_cycle" and not response ): log.show( "Cycle is not valid for Outlets in the off state" ) elif ( menu_actions[ch]["function"].__name__ == "pwr_rename" ): log.show( "rename not yet implemented for {} outlets".format( _type ) ) elif calling_menu in ["key_menu", "rename_menu"]: if response: log.show(response) else: # not confirmed log.show("Operation Aborted by User") elif menu_actions[ch].__name__ in ["power_menu", "dli_menu"]: menu_actions[ch](calling_menu=calling_menu) else: menu_actions[ch]() except KeyError as e: if len(choice.orig) <= 2 or not self.exec_shell_cmd(choice.orig): log.show(f"Invalid selection {e}, please try again.") return True