Stability updates for websocket and printer shutdown

This commit is contained in:
Jordan Ruthe 2020-11-16 18:18:11 -05:00
parent f223f74f7f
commit 87d346e541
4 changed files with 120 additions and 129 deletions

View File

@ -35,8 +35,8 @@ api = {
class KlippyWebsocket(threading.Thread): class KlippyWebsocket(threading.Thread):
_req_id = 0 _req_id = 0
connected = False connected = False
callback_table = {} callback_table = {}
timeout = None
def __init__(self, screen, callback): def __init__(self, screen, callback):
threading.Thread.__init__(self) threading.Thread.__init__(self)
@ -47,16 +47,17 @@ class KlippyWebsocket(threading.Thread):
self._url = "127.0.0.1:7125" self._url = "127.0.0.1:7125"
def connect (self): def connect (self):
r = requests.get( #r = requests.get(
"http://%s%s" % (self._url, api['oneshot_token']['url']), # "http://%s%s" % (self._url, api['oneshot_token']['url']),
headers={"x-api-key":api_key} # headers={"x-api-key":api_key}
) #)
if r.status_code != 200: #if r.status_code != 200:
logger.info("Failed to retrieve oneshot token") # logger.info("Failed to retrieve oneshot token")
return # return
token = json.loads(r.content)['result'] #token = json.loads(r.content)['result']
self.ws_url = "ws://%s/websocket?token=%s" % (self._url, token) #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, self.ws = websocket.WebSocketApp(self.ws_url,
on_message = lambda ws,msg: self.on_message(ws, msg), on_message = lambda ws,msg: self.on_message(ws, msg),
on_error = lambda ws,msg: self.on_error(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 = threading.Thread(target=self.ws.run_forever)
self._wst.daemon = True self._wst.daemon = True
self._wst.start() try:
self._wst.start()
except Exception:
logger.debug("Error starting web socket")
def is_connected(self): def is_connected(self):
return self.connected return self.connected
@ -86,16 +90,19 @@ class KlippyWebsocket(threading.Thread):
self.callback_table.pop(response['id']) self.callback_table.pop(response['id'])
return return
if "method" in response: if "method" in response and "on_message" in self._callback:
Gdk.threads_add_idle( Gdk.threads_add_idle(
GLib.PRIORITY_HIGH_IDLE, GLib.PRIORITY_HIGH_IDLE,
self._callback, self._callback['on_message'],
response['method'], response['method'],
response['params'][0] if "params" in response else {} response['params'][0] if "params" in response else {}
) )
return return
def send_method(self, method, params={}, callback=None, *args): def send_method(self, method, params={}, callback=None, *args):
if self.is_connected() == False:
return False
self._req_id += 1 self._req_id += 1
if callback != None: if callback != None:
self.callback_table[self._req_id] = [callback, method, params, [*args]] self.callback_table[self._req_id] = [callback, method, params, [*args]]
@ -107,38 +114,44 @@ class KlippyWebsocket(threading.Thread):
"id": self._req_id "id": self._req_id
} }
self.ws.send(json.dumps(data)) self.ws.send(json.dumps(data))
return True
def on_open(self, ws): 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.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): 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 self.connected = False
if self.timeout == None:
self.timeout = GLib.timeout_add(500, self.reconnect)
# TODO: Make non-blocking if "on_close" in self._callback:
Gdk.threads_add_idle( Gdk.threads_add_idle(
GLib.PRIORITY_HIGH_IDLE, GLib.PRIORITY_HIGH_IDLE,
self._screen.printer_initializing, self._callback['on_close'],
"Connecting to Moonraker..." "Lost Connection to Moonraker"
) )
while (True == True): def reconnect(self):
try: logger.debug("Attempting to reconnect")
info = self._screen.apiclient.get_info() if self.is_connected():
except Exception: logger.debug("Reconnected")
continue return False
if info != False:
self.connect()
if self.is_connected():
break
logger.info("### Waiting for websocket")
time.sleep(.5)
Gdk.threads_add_idle( self.connect()
GLib.PRIORITY_HIGH_IDLE, return True
self._screen.printer_ready
)
def on_error(self, ws, error): def on_error(self, ws, error):
@ -150,13 +163,13 @@ class MoonrakerApi:
def emergency_stop(self): def emergency_stop(self):
logger.info("Sending printer.emergency_stop") logger.info("Sending printer.emergency_stop")
self._ws.send_method( return self._ws.send_method(
"printer.emergency_stop" "printer.emergency_stop"
) )
def gcode_script(self, script, callback=None, *args): def gcode_script(self, script, callback=None, *args):
logger.debug("Sending printer.gcode.script: %s", script) logger.debug("Sending printer.gcode.script: %s", script)
self._ws.send_method( return self._ws.send_method(
"printer.gcode.script", "printer.gcode.script",
{"script": script}, {"script": script},
callback, callback,
@ -166,7 +179,7 @@ class MoonrakerApi:
def get_file_list(self, callback=None, *args): def get_file_list(self, callback=None, *args):
#Commenting this log for being too noisy #Commenting this log for being too noisy
#logger.debug("Sending server.files.list") #logger.debug("Sending server.files.list")
self._ws.send_method( return self._ws.send_method(
"server.files.list", "server.files.list",
{}, {},
callback, callback,
@ -175,7 +188,7 @@ class MoonrakerApi:
def get_file_metadata(self, filename, callback=None, *args): def get_file_metadata(self, filename, callback=None, *args):
logger.debug("Sending server.files.metadata: %s", filename) logger.debug("Sending server.files.metadata: %s", filename)
self._ws.send_method( return self._ws.send_method(
"server.files.metadata", "server.files.metadata",
{"filename": filename}, {"filename": filename},
callback, callback,
@ -184,14 +197,14 @@ class MoonrakerApi:
def object_subscription(self, updates): def object_subscription(self, updates):
logger.debug("Sending printer.objects.subscribe: %s", str(updates)) logger.debug("Sending printer.objects.subscribe: %s", str(updates))
self._ws.send_method( return self._ws.send_method(
"printer.objects.subscribe", "printer.objects.subscribe",
updates updates
) )
def print_cancel(self, callback=None, *args): def print_cancel(self, callback=None, *args):
logger.debug("Sending printer.print.cancel") logger.debug("Sending printer.print.cancel")
self._ws.send_method( return self._ws.send_method(
"printer.print.cancel", "printer.print.cancel",
{}, {},
callback, callback,
@ -200,7 +213,7 @@ class MoonrakerApi:
def print_pause(self, callback=None, *args): def print_pause(self, callback=None, *args):
logger.debug("Sending printer.print.pause") logger.debug("Sending printer.print.pause")
self._ws.send_method( return self._ws.send_method(
"printer.print.pause", "printer.print.pause",
{}, {},
callback, callback,
@ -209,7 +222,7 @@ class MoonrakerApi:
def print_resume(self, callback=None, *args): def print_resume(self, callback=None, *args):
logger.debug("Sending printer.print.resume") logger.debug("Sending printer.print.resume")
self._ws.send_method( return self._ws.send_method(
"printer.print.resume", "printer.print.resume",
{}, {},
callback, callback,
@ -218,7 +231,7 @@ class MoonrakerApi:
def print_start(self, filename, callback=None, *args): def print_start(self, filename, callback=None, *args):
logger.debug("Sending printer.print.start") logger.debug("Sending printer.print.start")
self._ws.send_method( return self._ws.send_method(
"printer.print.start", "printer.print.start",
{ {
"filename": filename "filename": filename
@ -230,7 +243,7 @@ class MoonrakerApi:
def temperature_set(self, heater, target, callback=None, *args): def temperature_set(self, heater, target, callback=None, *args):
if heater == "heater_bed": if heater == "heater_bed":
logger.debug("Sending printer.gcode.script: %s", KlippyGcodes.set_bed_temp(target)) logger.debug("Sending printer.gcode.script: %s", KlippyGcodes.set_bed_temp(target))
self._ws.send_method( return self._ws.send_method(
"printer.gcode.script", "printer.gcode.script",
{ {
"script": KlippyGcodes.set_bed_temp(target) "script": KlippyGcodes.set_bed_temp(target)
@ -242,7 +255,7 @@ class MoonrakerApi:
logger.debug("Sending printer.gcode.script: %s", logger.debug("Sending printer.gcode.script: %s",
KlippyGcodes.set_ext_temp(target, heater.replace("tool",""))) KlippyGcodes.set_ext_temp(target, heater.replace("tool","")))
#TODO: Add max/min limits #TODO: Add max/min limits
self._ws.send_method( return self._ws.send_method(
"printer.gcode.script", "printer.gcode.script",
{ {
"script": KlippyGcodes.set_ext_temp(target, heater.replace("tool","")) "script": KlippyGcodes.set_ext_temp(target, heater.replace("tool",""))
@ -253,7 +266,7 @@ class MoonrakerApi:
def set_bed_temp(self, target, callback=None, *args): def set_bed_temp(self, target, callback=None, *args):
logger.debug("Sending set_bed_temp: %s", KlippyGcodes.set_bed_temp(target)) logger.debug("Sending set_bed_temp: %s", KlippyGcodes.set_bed_temp(target))
self._ws.send_method( return self._ws.send_method(
"printer.gcode.script", "printer.gcode.script",
{ {
"script": KlippyGcodes.set_bed_temp(target) "script": KlippyGcodes.set_bed_temp(target)
@ -264,7 +277,7 @@ class MoonrakerApi:
def set_tool_temp(self, tool, target, callback=None, *args): def set_tool_temp(self, tool, target, callback=None, *args):
logger.debug("Sending set_tool_temp: %s", KlippyGcodes.set_ext_temp(target, tool)) 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", "printer.gcode.script",
{ {
"script": KlippyGcodes.set_ext_temp(target, tool) "script": KlippyGcodes.set_ext_temp(target, tool)
@ -275,12 +288,12 @@ class MoonrakerApi:
def restart(self): def restart(self):
logger.debug("Sending printer.restart") logger.debug("Sending printer.restart")
self._ws.send_method( return self._ws.send_method(
"printer.restart" "printer.restart"
) )
def restart_firmware(self): def restart_firmware(self):
logger.debug("Sending printer.firmware_restart") logger.debug("Sending printer.firmware_restart")
self._ws.send_method( return self._ws.send_method(
"printer.firmware_restart" "printer.firmware_restart"
) )

View File

@ -18,7 +18,7 @@ class KlippyFiles:
def __init__(self, screen): def __init__(self, screen):
self._screen = screen self._screen = screen
self.timeout = GLib.timeout_add(2000, self.ret_files) self.add_timeout()
if not os.path.exists('/tmp/.KS-thumbnails'): if not os.path.exists('/tmp/.KS-thumbnails'):
os.makedirs('/tmp/.KS-thumbnails') os.makedirs('/tmp/.KS-thumbnails')
@ -81,8 +81,18 @@ class KlippyFiles:
def add_file_callback(self, callback): def add_file_callback(self, callback):
self.callbacks.append(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): 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 return retval
def ret_file_data (self, filename): def ret_file_data (self, filename):

View File

@ -64,7 +64,7 @@ class SystemPanel(ScreenPanel):
#TODO: Shouldn't need this #TODO: Shouldn't need this
self.system_timeout = GLib.timeout_add(1000, self.update_system_load) 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": if type == "firmware":
self._screen._ws.klippy.restart_firmware() self._screen._ws.klippy.restart_firmware()
else: else:

118
screen.py
View File

@ -68,6 +68,7 @@ class KlipperScreen(Gtk.Window):
panels = {} panels = {}
_cur_panels = [] _cur_panels = []
files = None
filename = "" filename = ""
subscriptions = [] subscriptions = []
last_update = {} last_update = {}
@ -102,75 +103,20 @@ class KlipperScreen(Gtk.Window):
self.printer_initializing("Initializing") 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 # Disable DPMS
os.system("/usr/bin/xset -display :0 s off") os.system("/usr/bin/xset -display :0 s off")
os.system("/usr/bin/xset -display :0 -dpms") os.system("/usr/bin/xset -display :0 -dpms")
os.system("/usr/bin/xset -display :0 s noblank") 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): def ws_subscribe(self):
requested_updates = { requested_updates = {
@ -182,7 +128,8 @@ class KlipperScreen(Gtk.Window):
"heater_bed": ["target","temperature"], "heater_bed": ["target","temperature"],
"print_stats": ["print_duration","total_duration","filament_used","filename","state","message"], "print_stats": ["print_duration","total_duration","filament_used","filename","state","message"],
"toolhead": ["homed_axes","estimated_print_time","print_time","position","extruder"], "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) self._ws.klippy.object_subscription(requested_updates)
@ -317,12 +264,6 @@ class KlipperScreen(Gtk.Window):
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION 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): def _go_to_submenu(self, widget, name):
logger.info("#### Go to submenu " + str(name)) logger.info("#### Go to submenu " + str(name))
#self._remove_current_panel(False) #self._remove_current_panel(False)
@ -407,13 +348,18 @@ class KlipperScreen(Gtk.Window):
return return
elif action == "notify_klippy_ready": elif action == "notify_klippy_ready":
logger.info("### Going to ready state") logger.info("### Going to ready state")
self.printer_ready() self.init_printer()
elif action == "notify_status_update": elif action == "notify_status_update" and self.shutdown == False:
self.printer.process_update(data) 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 "webhooks" in data and "state" in data['webhooks']:
if data['webhooks']['state'] == "ready": if data['webhooks']['state'] == "ready":
logger.info("### Going to ready state") logger.info("### Going to ready state")
self.printer_ready() self.printer_ready()
elif data['webhooks']['state'] == "shutdown":
self.shutdown == True
self.printer_initializing("Klipper shutdown")
else: else:
active = self.printer.get_stat('virtual_sdcard','is_active') active = self.printer.get_stat('virtual_sdcard','is_active')
paused = self.printer.get_stat('pause_resume','is_paused') paused = self.printer.get_stat('pause_resume','is_paused')
@ -428,7 +374,7 @@ class KlipperScreen(Gtk.Window):
#self.files.add_file() #self.files.add_file()
elif action == "notify_metadata_update": elif action == "notify_metadata_update":
self.files.update_metadata(data['filename']) 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)): and re.search(r'B:[0-9\.]+\s/[0-9\.]+\sT[0-9]+:[0-9\.]+', data)):
logger.debug(json.dumps([action, data], indent=2)) 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'].update_text(text)
self.panels['splash_screen'].show_restart_buttons() self.panels['splash_screen'].show_restart_buttons()
def printer_ready(self): def init_printer(self):
self.shutdown = False self.shutdown = False
status_objects = [ status_objects = [
@ -461,8 +407,9 @@ class KlipperScreen(Gtk.Window):
'extruder', 'extruder',
'pause_resume' 'pause_resume'
] ]
info = self.apiclient.get_printer_info()
data = self.apiclient.send_request("printer/objects/query?" + "&".join(status_objects)) 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") self.printer_initializing("Moonraker error")
return return
data = data['result']['status'] data = data['result']['status']
@ -471,14 +418,35 @@ class KlipperScreen(Gtk.Window):
self.printer.__init__(data) self.printer.__init__(data)
self.ws_subscribe() 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"): if (data['print_stats']['state'] == "printing" or data['print_stats']['state'] == "paused"):
self.printer_printing() self.printer_printing()
return 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()) self.show_panel('main_panel', "MainPanel", 2, items=self._config['mainmenu'], extrudercount=self.printer.get_extruder_count())
def printer_printing(self): def printer_printing(self):
self.ws_subscribe() self.ws_subscribe()
self.files.remove_timeout()
self.show_panel('job_status',"JobStatusPanel", 2) self.show_panel('job_status',"JobStatusPanel", 2)
def get_software_version(): def get_software_version():