diff --git a/KlippyWebsocket.py b/KlippyWebsocket.py index ac63ca73..77d7b89b 100644 --- a/KlippyWebsocket.py +++ b/KlippyWebsocket.py @@ -35,8 +35,8 @@ api = { class KlippyWebsocket(threading.Thread): _req_id = 0 connected = False - callback_table = {} + timeout = None def __init__(self, screen, callback): threading.Thread.__init__(self) @@ -47,16 +47,17 @@ class KlippyWebsocket(threading.Thread): self._url = "127.0.0.1:7125" def connect (self): - r = requests.get( - "http://%s%s" % (self._url, api['oneshot_token']['url']), - headers={"x-api-key":api_key} - ) - if r.status_code != 200: - logger.info("Failed to retrieve oneshot token") - return + #r = requests.get( + # "http://%s%s" % (self._url, api['oneshot_token']['url']), + # headers={"x-api-key":api_key} + #) + #if r.status_code != 200: + # logger.info("Failed to retrieve oneshot token") + # return - token = json.loads(r.content)['result'] - self.ws_url = "ws://%s/websocket?token=%s" % (self._url, token) + #token = json.loads(r.content)['result'] + #self.ws_url = "ws://%s/websocket?token=%s" % (self._url, token) + self.ws_url = "ws://%s/websocket" % (self._url) self.ws = websocket.WebSocketApp(self.ws_url, on_message = lambda ws,msg: self.on_message(ws, msg), on_error = lambda ws,msg: self.on_error(ws, msg), @@ -66,7 +67,10 @@ class KlippyWebsocket(threading.Thread): self._wst = threading.Thread(target=self.ws.run_forever) self._wst.daemon = True - self._wst.start() + try: + self._wst.start() + except Exception: + logger.debug("Error starting web socket") def is_connected(self): return self.connected @@ -86,16 +90,19 @@ class KlippyWebsocket(threading.Thread): self.callback_table.pop(response['id']) return - if "method" in response: + if "method" in response and "on_message" in self._callback: Gdk.threads_add_idle( GLib.PRIORITY_HIGH_IDLE, - self._callback, + self._callback['on_message'], response['method'], response['params'][0] if "params" in response else {} ) return def send_method(self, method, params={}, callback=None, *args): + if self.is_connected() == False: + return False + self._req_id += 1 if callback != None: self.callback_table[self._req_id] = [callback, method, params, [*args]] @@ -107,38 +114,44 @@ class KlippyWebsocket(threading.Thread): "id": self._req_id } self.ws.send(json.dumps(data)) + return True def on_open(self, ws): - logger.info("### ws open ###") + logger.info("Moonraker Websocket Open") + logger.info("Self.connected = %s" % self.is_connected()) self.connected = True + self.timeout = None + if "on_connect" in self._callback: + Gdk.threads_add_idle( + GLib.PRIORITY_HIGH_IDLE, + self._callback['on_connect'] + ) def on_close(self, ws): - logger.info("### ws closed ###") + if self.is_connected() == False: + logger.debug("Connection already closed") + return + + logger.info("Moonraker Websocket Closed") self.connected = False + if self.timeout == None: + self.timeout = GLib.timeout_add(500, self.reconnect) - # TODO: Make non-blocking - Gdk.threads_add_idle( - GLib.PRIORITY_HIGH_IDLE, - self._screen.printer_initializing, - "Connecting to Moonraker..." - ) + if "on_close" in self._callback: + Gdk.threads_add_idle( + GLib.PRIORITY_HIGH_IDLE, + self._callback['on_close'], + "Lost Connection to Moonraker" + ) - while (True == True): - try: - info = self._screen.apiclient.get_info() - except Exception: - continue - if info != False: - self.connect() - if self.is_connected(): - break - logger.info("### Waiting for websocket") - time.sleep(.5) + def reconnect(self): + logger.debug("Attempting to reconnect") + if self.is_connected(): + logger.debug("Reconnected") + return False - Gdk.threads_add_idle( - GLib.PRIORITY_HIGH_IDLE, - self._screen.printer_ready - ) + self.connect() + return True def on_error(self, ws, error): @@ -150,13 +163,13 @@ class MoonrakerApi: def emergency_stop(self): logger.info("Sending printer.emergency_stop") - self._ws.send_method( + return self._ws.send_method( "printer.emergency_stop" ) def gcode_script(self, script, callback=None, *args): logger.debug("Sending printer.gcode.script: %s", script) - self._ws.send_method( + return self._ws.send_method( "printer.gcode.script", {"script": script}, callback, @@ -166,7 +179,7 @@ class MoonrakerApi: def get_file_list(self, callback=None, *args): #Commenting this log for being too noisy #logger.debug("Sending server.files.list") - self._ws.send_method( + return self._ws.send_method( "server.files.list", {}, callback, @@ -175,7 +188,7 @@ class MoonrakerApi: def get_file_metadata(self, filename, callback=None, *args): logger.debug("Sending server.files.metadata: %s", filename) - self._ws.send_method( + return self._ws.send_method( "server.files.metadata", {"filename": filename}, callback, @@ -184,14 +197,14 @@ class MoonrakerApi: def object_subscription(self, updates): logger.debug("Sending printer.objects.subscribe: %s", str(updates)) - self._ws.send_method( + return self._ws.send_method( "printer.objects.subscribe", updates ) def print_cancel(self, callback=None, *args): logger.debug("Sending printer.print.cancel") - self._ws.send_method( + return self._ws.send_method( "printer.print.cancel", {}, callback, @@ -200,7 +213,7 @@ class MoonrakerApi: def print_pause(self, callback=None, *args): logger.debug("Sending printer.print.pause") - self._ws.send_method( + return self._ws.send_method( "printer.print.pause", {}, callback, @@ -209,7 +222,7 @@ class MoonrakerApi: def print_resume(self, callback=None, *args): logger.debug("Sending printer.print.resume") - self._ws.send_method( + return self._ws.send_method( "printer.print.resume", {}, callback, @@ -218,7 +231,7 @@ class MoonrakerApi: def print_start(self, filename, callback=None, *args): logger.debug("Sending printer.print.start") - self._ws.send_method( + return self._ws.send_method( "printer.print.start", { "filename": filename @@ -230,7 +243,7 @@ class MoonrakerApi: def temperature_set(self, heater, target, callback=None, *args): if heater == "heater_bed": logger.debug("Sending printer.gcode.script: %s", KlippyGcodes.set_bed_temp(target)) - self._ws.send_method( + return self._ws.send_method( "printer.gcode.script", { "script": KlippyGcodes.set_bed_temp(target) @@ -242,7 +255,7 @@ class MoonrakerApi: logger.debug("Sending printer.gcode.script: %s", KlippyGcodes.set_ext_temp(target, heater.replace("tool",""))) #TODO: Add max/min limits - self._ws.send_method( + return self._ws.send_method( "printer.gcode.script", { "script": KlippyGcodes.set_ext_temp(target, heater.replace("tool","")) @@ -253,7 +266,7 @@ class MoonrakerApi: def set_bed_temp(self, target, callback=None, *args): logger.debug("Sending set_bed_temp: %s", KlippyGcodes.set_bed_temp(target)) - self._ws.send_method( + return self._ws.send_method( "printer.gcode.script", { "script": KlippyGcodes.set_bed_temp(target) @@ -264,7 +277,7 @@ class MoonrakerApi: def set_tool_temp(self, tool, target, callback=None, *args): logger.debug("Sending set_tool_temp: %s", KlippyGcodes.set_ext_temp(target, tool)) - self._ws.send_method( + return self._ws.send_method( "printer.gcode.script", { "script": KlippyGcodes.set_ext_temp(target, tool) @@ -275,12 +288,12 @@ class MoonrakerApi: def restart(self): logger.debug("Sending printer.restart") - self._ws.send_method( + return self._ws.send_method( "printer.restart" ) def restart_firmware(self): logger.debug("Sending printer.firmware_restart") - self._ws.send_method( + return self._ws.send_method( "printer.firmware_restart" ) diff --git a/files.py b/files.py index 01cfb6d5..86abd9ce 100644 --- a/files.py +++ b/files.py @@ -18,7 +18,7 @@ class KlippyFiles: def __init__(self, screen): self._screen = screen - self.timeout = GLib.timeout_add(2000, self.ret_files) + self.add_timeout() if not os.path.exists('/tmp/.KS-thumbnails'): os.makedirs('/tmp/.KS-thumbnails') @@ -81,8 +81,18 @@ class KlippyFiles: def add_file_callback(self, callback): self.callbacks.append(callback) + def add_timeout(self): + if self.timeout == None: + self.timeout = GLib.timeout_add(4000, self.ret_files) + + def remove_timeout(self): + if self.timeout != None: + self.timeout = None + def ret_files(self, retval=True): - self._screen._ws.klippy.get_file_list(self._callback) + if not self._screen._ws.klippy.get_file_list(self._callback): + self.timeout = None + return False return retval def ret_file_data (self, filename): diff --git a/panels/system.py b/panels/system.py index 68a53299..b64a10be 100644 --- a/panels/system.py +++ b/panels/system.py @@ -64,7 +64,7 @@ class SystemPanel(ScreenPanel): #TODO: Shouldn't need this self.system_timeout = GLib.timeout_add(1000, self.update_system_load) - def restart_klippy(self, type=None): + def restart_klippy(self, widget, type=None): if type == "firmware": self._screen._ws.klippy.restart_firmware() else: diff --git a/screen.py b/screen.py index 494c3a5c..26313ca9 100644 --- a/screen.py +++ b/screen.py @@ -68,6 +68,7 @@ class KlipperScreen(Gtk.Window): panels = {} _cur_panels = [] + files = None filename = "" subscriptions = [] last_update = {} @@ -102,75 +103,20 @@ class KlipperScreen(Gtk.Window): self.printer_initializing("Initializing") + self._ws = KlippyWebsocket(self, { + "on_connect": self.init_printer, + "on_message": self._websocket_callback, + "on_close": self.printer_initializing + }) + self._ws.connect() + # Disable DPMS os.system("/usr/bin/xset -display :0 s off") os.system("/usr/bin/xset -display :0 -dpms") os.system("/usr/bin/xset -display :0 s noblank") - ready = False + return - try: - info = self.apiclient.get_server_info() - except Exception: - return - - self.printer_initializing("Initializing") - - if info == False: - return - - if not hasattr(self, "_ws"): - self.create_websocket() - - print(info) - if info['result']['klippy_state'] == "shutdown": - self.printer_initializing("Klipper is shutdown") - return - if info['result']['klippy_state'] == "error": - logger.warning("Printer is emergency stopped") - self.printer_initializing("Shutdown due to Emergency Stop") - return - - status_objects = [ - 'idle_timeout', - 'configfile', - 'gcode_move', - 'fan', - 'toolhead', - 'virtual_sdcard', - 'print_stats', - 'heater_bed', - 'extruder', - 'pause_resume' - ] - r = requests.get("http://127.0.0.1:7125/printer/objects/query?" + "&".join(status_objects)) - - #TODO: Check that we get good data - #print (r.content) - try: - data = json.loads(r.content) - except: - logger.info("Not able to load data. Klippy is most likely offline") - return - data = data['result']['status'] - for x in data: - self.last_update[x] = data[x] - - self.printer_config = data['configfile']['config'] - #logger.debug("Printer config: %s" % json.dumps(self.printer_config, indent=2)) - self.printer.__init__(data) - - # Initialize target values. TODO: methodize this - self.printer.set_dev_stat("heater_bed", "target", data['heater_bed']['target']) - self.printer.set_dev_stat("extruder", "target", data['extruder']['target']) - - print (info) - if (data['print_stats']['state'] == "printing" or data['print_stats']['state'] == "paused"): - self.printer_printing() - elif info['result']['klippy_state'] == "ready": - self.printer_ready() - - self.files = KlippyFiles(self) def ws_subscribe(self): requested_updates = { @@ -182,7 +128,8 @@ class KlipperScreen(Gtk.Window): "heater_bed": ["target","temperature"], "print_stats": ["print_duration","total_duration","filament_used","filename","state","message"], "toolhead": ["homed_axes","estimated_print_time","print_time","position","extruder"], - "virtual_sdcard": ["file_position","is_active","progress"] + "virtual_sdcard": ["file_position","is_active","progress"], + "webhooks": ["state","state_message"] } } self._ws.klippy.object_subscription(requested_updates) @@ -317,12 +264,6 @@ class KlipperScreen(Gtk.Window): Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION ) - def create_websocket(self): - self._ws = KlippyWebsocket(self, self._websocket_callback) - self._ws.connect() - self._curr = 0 - - def _go_to_submenu(self, widget, name): logger.info("#### Go to submenu " + str(name)) #self._remove_current_panel(False) @@ -407,13 +348,18 @@ class KlipperScreen(Gtk.Window): return elif action == "notify_klippy_ready": logger.info("### Going to ready state") - self.printer_ready() - elif action == "notify_status_update": + self.init_printer() + elif action == "notify_status_update" and self.shutdown == False: 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 shutdown") else: active = self.printer.get_stat('virtual_sdcard','is_active') paused = self.printer.get_stat('pause_resume','is_paused') @@ -428,7 +374,7 @@ class KlipperScreen(Gtk.Window): #self.files.add_file() elif action == "notify_metadata_update": self.files.update_metadata(data['filename']) - elif not (action == "notify_gcode_response" and data.startswith("B:") + elif self.shutdown == False and not (action == "notify_gcode_response" and data.startswith("B:") and re.search(r'B:[0-9\.]+\s/[0-9\.]+\sT[0-9]+:[0-9\.]+', data)): logger.debug(json.dumps([action, data], indent=2)) @@ -446,7 +392,7 @@ class KlipperScreen(Gtk.Window): self.panels['splash_screen'].update_text(text) self.panels['splash_screen'].show_restart_buttons() - def printer_ready(self): + def init_printer(self): self.shutdown = False status_objects = [ @@ -461,8 +407,9 @@ class KlipperScreen(Gtk.Window): 'extruder', 'pause_resume' ] + info = self.apiclient.get_printer_info() data = self.apiclient.send_request("printer/objects/query?" + "&".join(status_objects)) - if data == False: + if info == False or data == False: self.printer_initializing("Moonraker error") return data = data['result']['status'] @@ -471,14 +418,35 @@ class KlipperScreen(Gtk.Window): self.printer.__init__(data) self.ws_subscribe() + if self.files == None: + self.files = KlippyFiles(self) + else: + self.files.add_timeout() + + if info['result']['state'] == "shutdown": + if "FIRMWARE_RESTART" in info['result']['state_message']: + self.printer_initializing( + "Klipper has encountered an error. Issue a FIRMWARE_RESTART to attempt fixing the issue." + ) + else: + self.printer_initializing("Klippy is shutdown") + return if (data['print_stats']['state'] == "printing" or data['print_stats']['state'] == "paused"): self.printer_printing() return + self.printer_ready() + def printer_ready(self): + if self.shutdown == True: + self.init_printer() + return + + self.files.add_timeout() self.show_panel('main_panel', "MainPanel", 2, items=self._config['mainmenu'], extrudercount=self.printer.get_extruder_count()) def printer_printing(self): self.ws_subscribe() + self.files.remove_timeout() self.show_panel('job_status',"JobStatusPanel", 2) def get_software_version():