From c58b040be22424a011313486c192231c6727c74f Mon Sep 17 00:00:00 2001 From: Jordan <31575189+jordanruthe@users.noreply.github.com> Date: Mon, 28 Dec 2020 16:21:15 -0500 Subject: [PATCH] printer: change how states are transitioned --- ks_includes/printer.py | 82 ++++++++++++++++++++++------ screen.py | 121 ++++++++++++++++++++--------------------- 2 files changed, 124 insertions(+), 79 deletions(-) diff --git a/ks_includes/printer.py b/ks_includes/printer.py index 962235da..640d0170 100644 --- a/ks_includes/printer.py +++ b/ks_includes/printer.py @@ -3,20 +3,29 @@ import logging logger = logging.getLogger("KlipperScreen.Printer") class Printer: + state_callbacks = { + "disconnected": None, + "error": None, + "paused": None, + "printing": None, + "ready": None, + "startup": None, + "shutdown": None + } def __init__(self, printer_info, data): - self.config = data['configfile']['config'] + self.state = "disconnected" + self.power_devices = {} - logger.info("### Reading printer config") + def reinit(self, printer_info, data): logger.debug("Moonraker object status: %s" % data) + self.config = data['configfile']['config'] self.toolcount = 0 self.extrudercount = 0 self.tools = [] self.devices = {} - self.state = data['print_stats']['state'] self.data = data self.klipper = {} - self.power_devices = {} self.klipper = { "version": printer_info['software_version'] @@ -56,17 +65,6 @@ class Printer: logger.info("Klipper version: %s", self.klipper['version']) logger.info("### Toolcount: " + str(self.toolcount) + " Heaters: " + str(self.extrudercount)) - def configure_power_devices(self, data): - self.power_devices = {} - - logger.debug("Processing power devices: %s" % data) - for x in data['devices']: - logger.debug(x) - self.power_devices[x['device']] = { - "status": "on" if x['status'] == "on" else "off" - } - logger.debug("Power devices: %s" % self.power_devices) - def process_update(self, data): keys = [ 'bed_mesh', @@ -76,7 +74,8 @@ class Printer: 'pause_resume', 'print_stats', 'toolhead', - 'virtual_sdcard' + 'virtual_sdcard', + 'webhooks' ] for x in keys: if x in data: @@ -100,10 +99,53 @@ class Printer: if "temperature" in d: self.set_dev_stat(x, "temperature", d["temperature"]) + if "webhooks" in data or "idle_timeout" in data or "pause_resume" in data or "print_stats" in data: + self.evaluate_state() + + def evaluate_state(self): + wh_state = self.data['webhooks']['state'] # possible values: startup, ready, shutdown, error + idle_state = self.data['idle_timeout']['state'].lower() # possible values: Idle, printing, ready + print_state = self.data['print_stats']['state'] # possible values: complete, paused, printing, standby + + if wh_state == "ready": + new_state = "ready" + if idle_state == "printing" and print_state != "printing": # Not printing a file, toolhead moving + new_state = "busy" + elif idle_state == "printing" and print_state == "printing": + new_state = "printing" + elif print_state == "paused": + new_state = "paused" + + if new_state != "busy": + self.change_state(new_state) + else: + self.change_state(wh_state) + def process_power_update(self, data): if data['device'] in self.power_devices: self.power_devices[data['device']]['status'] = data['status'] + def change_state(self, state): + if state == self.state or state not in list(self.state_callbacks): + return + + logger.debug("Changing state from '%s' to '%s'" % (self.state, state)) + self.state = state + if self.state_callbacks[state] != None: + logger.debug("Running callback for state: %s" % state) + self.state_callbacks[state]() + + def configure_power_devices(self, data): + self.power_devices = {} + + logger.debug("Processing power devices: %s" % data) + for x in data['devices']: + logger.debug(x) + self.power_devices[x['device']] = { + "status": "on" if x['status'] == "on" else "off" + } + logger.debug("Power devices: %s" % self.power_devices) + def config_section_exists(self, section): return section in list(self.config) @@ -164,6 +206,9 @@ class Printer: return None return self.data[stat] + def get_state(self): + return self.state + def set_dev_temps(self, dev, temp, target=None): if dev in self.devices: self.devices[dev]['temperature'] = temp @@ -194,6 +239,11 @@ class Printer: return True return False + def set_callbacks(self, callbacks): + for name, cb in callbacks.items(): + if name in list(self.state_callbacks): + self.state_callbacks[name] = cb + def set_dev_stat(self, dev, stat, value): if dev not in self.devices: return diff --git a/screen.py b/screen.py index 7f4cffeb..c73cfed2 100644 --- a/screen.py +++ b/screen.py @@ -90,6 +90,15 @@ class KlipperScreen(Gtk.Window): 'is_active': False } }) + self.printer.set_callbacks({ + "disconnected": self.state_disconnected, + "error": self.state_error, + "printing": self.state_printing, + "ready": self.state_ready, + "startup": self.state_startup, + "shutdown": self.state_shutdown + }) + self.lang = gettext.translation('KlipperScreen', localedir='ks_includes/locales', fallback=True) _ = self.lang.gettext @@ -138,6 +147,7 @@ class KlipperScreen(Gtk.Window): "fan": ["speed"], "gcode_move": ["extrude_factor","gcode_position","homing_origin","speed_factor"], "heater_bed": ["target","temperature"], + "idle_timeout": ["state"], "pause_resume": ["is_paused"], "print_stats": ["print_duration","total_duration","filament_used","filename","state","message"], "toolhead": ["homed_axes","estimated_print_time","print_time","position","extruder"], @@ -353,34 +363,54 @@ class KlipperScreen(Gtk.Window): self.subscriptions.pop(i) return + def state_disconnected(self): + _ = self.lang.gettext + logger.debug("### Going to disconnected") + self.printer_initializing(_("Klipper has disconnected")) + + def state_error(self): + _ = self.lang.gettext + msg = self.printer.get_stat("webhooks","state_message") + if "FIRMWARE_RESTART" in msg: + self.printer_initializing( + _("Klipper has encountered an error.\nIssue a FIRMWARE_RESTART to attempt fixing the issue.") + ) + elif "micro-controller" in msg: + self.printer_initializing( + _("Klipper has encountered an error with the micro-controller.\nPlease recompile and flash.") + ) + else: + self.printer_initializing( + _("Klipper has encountered an error.") + ) + + def state_printing(self): + self.printer_printing() + + def state_ready(self): + # Do not return to main menu if completing a job, timeouts/user input will return + if "job_status" in self._cur_panels or "main_menu" in self._cur_panels: + return + self.printer_ready() + + def state_startup(self): + _ = self.lang.gettext + self.printer_initializing(_("Klipper is attempting to start")) + + def state_shutdown(self): + _ = self.lang.gettext + self.printer_initializing(_("Klipper has shutdown")) + def _websocket_callback(self, action, data): _ = self.lang.gettext - #print(json.dumps([action, data], indent=2)) if action == "notify_klippy_disconnected": - logger.info("### Going to disconnected state") - self.printer_initializing(_("Klipper has shutdown")) + self.printer.change_state("disconnected") return elif action == "notify_klippy_ready": - logger.info("### Going to ready state") - self.init_printer() - elif action == "notify_status_update" and self.shutdown == False: + self.printer.change_state("ready") + elif action == "notify_status_update" and self.printer.get_state() != "shutdown": self.printer.process_update(data) - if "webhooks" in data: - print(json.dumps([action, data], indent=2)) - if "webhooks" in data and "state" in data['webhooks']: - if data['webhooks']['state'] == "ready": - logger.info("### Going to ready state") - self.printer_ready() - elif data['webhooks']['state'] == "shutdown": - self.shutdown == True - self.printer_initializing(_("Klipper has shutdown")) - else: - active = self.printer.get_stat('virtual_sdcard','is_active') - paused = self.printer.get_stat('pause_resume','is_paused') - if "job_status" not in self._cur_panels: - if active == True or paused == True: - self.printer_printing() elif action == "notify_filelist_changed": logger.debug("Filelist changed: %s", json.dumps(data,indent=2)) #self.files.add_file() @@ -389,10 +419,9 @@ class KlipperScreen(Gtk.Window): elif action == "notify_power_changed": logger.debug("Power status changed: %s", data) self.printer.process_power_update(data) - elif self.shutdown == False and action == "notify_gcode_response": + elif self.printer.get_state() not in ["error","shutdown"] and action == "notify_gcode_response": if "Klipper state: Shutdown" in data: - self.shutdown == True - self.printer_initializing(_("Klipper has shutdown")) + self.printer.change_state("shutdown") if not (data.startswith("B:") and re.search(r'B:[0-9\.]+\s/[0-9\.]+\sT[0-9]+:[0-9\.]+', data)): @@ -439,7 +468,6 @@ class KlipperScreen(Gtk.Window): def init_printer(self): _ = self.lang.gettext - self.shutdown = False status_objects = [ 'bed_mesh', @@ -452,18 +480,17 @@ class KlipperScreen(Gtk.Window): 'print_stats', 'heater_bed', 'extruder', - 'pause_resume' + 'pause_resume', + 'webhooks' ] printer_info = self.apiclient.get_printer_info() + logger.debug("Sendingn request %s" % "printer/objects/query?" + "&".join(status_objects)) data = self.apiclient.send_request("printer/objects/query?" + "&".join(status_objects)) powerdevs = self.apiclient.send_request("machine/device_power/devices") - if printer_info == False or data == False: - self.printer_initializing(_("Moonraker error")) - return data = data['result']['status'] # Reinitialize printer, in case the printer was shut down and anything has changed. - self.printer.__init__(printer_info['result'], data) + self.printer.reinit(printer_info['result'], data) self.ws_subscribe() if powerdevs != False: @@ -474,48 +501,16 @@ class KlipperScreen(Gtk.Window): else: self.files.add_timeout() - if printer_info['result']['state'] in ("error","shutdown","startup"): - if printer_info['result']['state'] == "startup": - self.printer_initializing(_("Klipper is attempting to start")) - elif printer_info['result']['state'] == "error": - if "FIRMWARE_RESTART" in printer_info['result']['state_message']: - self.printer_initializing( - _("Klipper has encountered an error.\nIssue a FIRMWARE_RESTART to attempt fixing the issue.") - ) - elif "micro-controller" in printer_info['result']['state_message']: - self.printer_initializing( - _("Klipper has encountered an error with the micro-controller.\nPlease recompile and flash.") - ) - else: - self.printer_initializing( - _("Klipper has encountered an error.") - ) - else: - self.printer_initializing(_("Klipper has shutdown")) - return - if (data['print_stats']['state'] == "printing" or data['print_stats']['state'] == "paused"): - filename = self.printer.get_stat("print_stats","filename") - if not self.files.file_metadata_exists(filename): - self.files.request_metadata(filename) - self.printer_printing() - return - self.printer_ready() - def printer_ready(self): - if self.shutdown == True: - self.init_printer() - return - - self.files.add_timeout() self.close_popup_message() self.show_panel('main_panel', "main_menu", "Main Menu", 2, items=self._config.get_menu_items("__main"), extrudercount=self.printer.get_extruder_count()) + self.ws_subscribe() if "job_status" in self.panels: self.remove_subscription("job_status") del self.panels["job_status"] def printer_printing(self): - self.ws_subscribe() self.files.remove_timeout() self.close_popup_message() self.show_panel('job_status',"job_status", "Print Status", 2)