diff --git a/README.md b/README.md index 05403585..e191fa86 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,9 @@ # KlipperScreen -KlipperScreen is an idea based from [OctoScreen](https://github.com/Z-Bolt/OctoScreen/), but instead of needing OctoPrint or to compile go, KlipperScreen is python based and interacts directly with [Moonraker](https://github.com/arksine/moonraker), Klipper's API service. -Current feature list: - - [x] Homing - - [x] Preheating - - [x] Job Status and control - - [x] Temperature control - - [x] Extrude control - - [x] Fan control - - [x] Disable steppers - - [x] Configure Z Offset using PROBE_CALIBRATE - - [x] Print tuning (Z Babystepping, Speed Control, Flow Control) - - [x] Manual bed leveling assist - - [x] Using thumbnails from prusa on job status page - - [x] Scale UI based off of resolution - - [ ] Better system panel - - [ ] Wifi selection +KlipperScreen is touchscreen GUI for Klipper based 3D printers. KlipperScreen interfaces with [Klipper](https://github.com/KevinOConner/klipper) via [Moonraker](https://github.com/arksine/moonraker). + +Multiple printers update is here! Please check the configuration information for specifying several printers. + [Changelog](docs/changelog.md) @@ -43,3 +31,8 @@ Main Menu Job Status ![Job Status](docs/img/job_status.png) + + +### Inspiration +KlipperScreen was inspired by [OctoScreen](https://github.com/Z-Bolt/OctoScreen/) and the need for a touchscreen GUI that +will natively work with [Klipper](https://github.com/KevinOConner/klipper) and [Moonraker](https://github.com/arksine/moonraker). diff --git a/docs/changelog.md b/docs/changelog.md index 6bf7ec0c..301ec703 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,5 +1,8 @@ ## Changelog +#### 2021 03 05 +* Multiple printers are now available in the main branch. + #### 2021 02 22 * Add configurable z_babystep intervals * Add QUAD_GANTRY_LEVEL and Z_TILT_ADJUST to Homing menu diff --git a/ks_includes/KlippyWebsocket.py b/ks_includes/KlippyWebsocket.py index a385c2e4..1c19509e 100644 --- a/ks_includes/KlippyWebsocket.py +++ b/ks_includes/KlippyWebsocket.py @@ -75,6 +75,7 @@ class KlippyWebsocket(threading.Thread): def close(self): self.closing = True + self.ws.close() def is_connected(self): return self.connected diff --git a/ks_includes/config.py b/ks_includes/config.py index 881f801b..b861fc59 100644 --- a/ks_includes/config.py +++ b/ks_includes/config.py @@ -88,7 +88,7 @@ class KlipperScreenConfig: printer[8:]: { "moonraker_host": self.config.get(printer, "moonraker_host", fallback="127.0.0.1"), "moonraker_port": self.config.get(printer, "moonraker_port", fallback="7125"), - "moonraker_api_key": self.config.get(printer, "moonraker_api_key", fallback="") + "moonraker_api_key": self.config.get(printer, "moonraker_api_key", fallback=False) } }) if len(printers) <= 0: @@ -138,6 +138,7 @@ class KlipperScreenConfig: return ["\n".join(user_def), None if saved_def == None else "\n".join(saved_def)] def get_config_file_location(self, file): + logging.info("Passed config file: %s" % file) if not path.exists(file): file = "%s/%s" % (os.getcwd(), self.configfile_name) if not path.exists(file): @@ -219,16 +220,17 @@ class KlipperScreenConfig: save_config.add_section(opt['section']) save_config.set(opt['section'], name, str(curval)) - if "displayed_macros" in self.config.sections(): - for item in self.config.options('displayed_macros'): - value = self.config['displayed_macros'].getboolean(item, fallback=True) + macro_sections = [i for i in self.config.sections() if i.startswith("displayed_macros")] + for macro_sec in macro_sections: + for item in self.config.options(macro_sec): + value = self.config[macro_sec].getboolean(item, fallback=True) if value == False or (self.defined_config != None and - "displayed_macros" in self.defined_config.sections() and - self.defined_config['displayed_macros'].getboolean(item, fallback=True) == False and - self.defined_config['displayed_macros'].getboolean(item, fallback=True) != value): - if "displayed_macros" not in save_config.sections(): - save_config.add_section("displayed_macros") - save_config.set("displayed_macros", item, str(value)) + macro_sec in self.defined_config.sections() and + self.defined_config[macro_sec].getboolean(item, fallback=True) == False and + self.defined_config[macro_sec].getboolean(item, fallback=True) != value): + if macro_sec not in save_config.sections(): + save_config.add_section(macro_sec) + save_config.set(macro_sec, item, str(value)) save_output = self._build_config_string(save_config).split("\n") for i in range(len(save_output)): diff --git a/ks_includes/files.py b/ks_includes/files.py index 0bc3f1c9..d4bcd8d7 100644 --- a/ks_includes/files.py +++ b/ks_includes/files.py @@ -14,11 +14,6 @@ from gi.repository import Gtk, Gdk, GLib RESCAN_INTERVAL = 4 class KlippyFiles(Thread): - callbacks = [] - filelist = [] - files = {} - metadata_timeout = {} - timeout = None thumbnail_dir = "/tmp/.KS-thumbnails" def __init__(self, screen, *args, **kwargs): @@ -26,6 +21,12 @@ class KlippyFiles(Thread): self.loop = None self._poll_task = None self._screen = screen + self.callbacks = [] + self.files = {} + self.filelist = [] + self.metadata_timeout = {} + + logging.info("") if not os.path.exists(self.thumbnail_dir): os.makedirs(self.thumbnail_dir) @@ -43,9 +44,11 @@ class KlippyFiles(Thread): with suppress(asyncio.CancelledError): loop.run_until_complete(self._poll_task) finally: + logging.info("Closing loop") loop.close() def stop(self): + logging.info("Trying to stop loop2") self.loop.call_soon_threadsafe(self.loop.stop) async def _poll(self): @@ -117,10 +120,6 @@ class KlippyFiles(Thread): if callback in self.callbacks: self.callbacks.pop(self.callbacks.index(callback)) - def add_timeout(self): - if self.timeout == None: - self.timeout = GLib.timeout_add(4000, self.ret_files) - def file_exists(self, filename): return True if filename in self.filelist else False @@ -141,18 +140,13 @@ class KlippyFiles(Thread): return False return "thumbnails" in self.files[filename] and len(self.files[filename]) > 0 - def remove_timeout(self): - if self.timeout != None: - self.timeout = None - def request_metadata(self, filename): if filename not in self.filelist: return False self._screen._ws.klippy.get_file_metadata(filename, self._callback) async def ret_files(self, retval=True): - if not self._screen._ws.klippy.get_file_list(self._callback): - self.timeout = None + self._screen._ws.klippy.get_file_list(self._callback) def ret_file_data (self, filename): print("Getting file info for %s" % (filename)) diff --git a/ks_includes/printer.py b/ks_includes/printer.py index e5c4ee3d..9af1d6d6 100644 --- a/ks_includes/printer.py +++ b/ks_includes/printer.py @@ -7,6 +7,9 @@ from ks_includes.KlippyGcodes import KlippyGcodes class Printer: + data = {} + devices = {} + power_devices = {} state_callbacks = { "disconnected": None, "error": None, @@ -16,6 +19,7 @@ class Printer: "startup": None, "shutdown": None } + tools = [] def __init__(self, printer_info, data): self.state = "disconnected" @@ -148,6 +152,8 @@ class Printer: return section in list(self.config) def get_config_section_list(self, search=""): + if not hasattr(self, "config"): + return [] return [i for i in list(self.config) if i.startswith(search)] def get_config_section(self, section): diff --git a/ks_includes/screen_panel.py b/ks_includes/screen_panel.py index 949713ae..e4425480 100644 --- a/ks_includes/screen_panel.py +++ b/ks_includes/screen_panel.py @@ -12,7 +12,7 @@ class ScreenPanel: title_spacing = 50 control = {} - def __init__(self, screen, title, back=True): + def __init__(self, screen, title, back=True, action_bar=True, printer_name=True): self._screen = screen self._config = screen._config self._files = screen.files @@ -24,34 +24,41 @@ class ScreenPanel: self.layout = Gtk.Layout() self.layout.set_size(self._screen.width, self._screen.height) - action_bar_width = self._gtk.get_action_bar_width() + action_bar_width = self._gtk.get_action_bar_width() if action_bar == True else 0 - self.control_grid = self._gtk.HomogeneousGrid() - self.control_grid.set_size_request(action_bar_width - 2, self._screen.height) - self.control_grid.get_style_context().add_class('action_bar') + if action_bar == True: + self.control_grid = self._gtk.HomogeneousGrid() + self.control_grid.set_size_request(action_bar_width - 2, self._screen.height) + self.control_grid.get_style_context().add_class('action_bar') - button_scale = self._gtk.get_header_image_scale() - logging.debug("Button scale: %s" % button_scale) - if back == True: - self.control['back'] = self._gtk.ButtonImage('back', None, None, button_scale[0], button_scale[1]) - self.control['back'].connect("clicked", self._screen._menu_go_back) - self.control_grid.attach(self.control['back'], 0, 0, 1, 1) + button_scale = self._gtk.get_header_image_scale() + logging.debug("Button scale: %s" % button_scale) - self.control['home'] = self._gtk.ButtonImage('home', None, None, button_scale[0], button_scale[1]) - self.control['home'].connect("clicked", self.menu_return, True) - self.control_grid.attach(self.control['home'], 0, 1, 1, 1) + if back == True: + self.control['back'] = self._gtk.ButtonImage('back', None, None, button_scale[0], button_scale[1]) + self.control['back'].connect("clicked", self._screen._menu_go_back) + self.control_grid.attach(self.control['back'], 0, 0, 1, 1) - self.control['printer_select'] = Gtk.Label("") + self.control['home'] = self._gtk.ButtonImage('home', None, None, button_scale[0], button_scale[1]) + self.control['home'].connect("clicked", self.menu_return, True) + self.control_grid.attach(self.control['home'], 0, 1, 1, 1) + else: + for i in range(2): + self.control['space%s' % i] = Gtk.Label("") + self.control_grid.attach(self.control['space%s' % i], 0, i, 1, 1) + + if len(self._config.get_printers()) > 1: + self.control['printer_select'] = self._gtk.ButtonImage( + 'shuffle', None, None, button_scale[0], button_scale[1]) + self.control['printer_select'].connect("clicked", self._screen.show_printer_select) + else: + self.control['printer_select'] = Gtk.Label("") self.control_grid.attach(self.control['printer_select'], 0, 2, 1, 1) - else: - for i in range(3): - self.control['space%s' % i] = Gtk.Label("") - self.control_grid.attach(self.control['space%s' % i], 0, i, 1, 1) - self.control['estop'] = self._gtk.ButtonImage('emergency', None, None, button_scale[0], button_scale[1]) - self.control['estop'].connect("clicked", self.emergency_stop) - self.control_grid.attach(self.control['estop'], 0, 3, 1, 1) - #self.layout.put(self.control['estop'], int(self._screen.width/4*3 - button_scale[0]/2), 0) + self.control['estop'] = self._gtk.ButtonImage('emergency', None, None, button_scale[0], button_scale[1]) + self.control['estop'].connect("clicked", self.emergency_stop) + self.control_grid.attach(self.control['estop'], 0, 3, 1, 1) + #self.layout.put(self.control['estop'], int(self._screen.width/4*3 - button_scale[0]/2), 0) try: env = Environment(extensions=["jinja2.ext.i18n"]) @@ -66,13 +73,16 @@ class ScreenPanel: self.title.set_hexpand(True) self.title.set_halign(Gtk.Align.CENTER) self.title.set_valign(Gtk.Align.CENTER) - self.set_title(title) - + if printer_name == True: + self.set_title("%s | %s" % (self._screen.connected_printer, title)) + else: + self.set_title(title) self.content = Gtk.Box(spacing=0) self.content.set_size_request(self._screen.width - action_bar_width, self._screen.height - self.title_spacing) - self.layout.put(self.control_grid, 0, 0) + if action_bar == True: + self.layout.put(self.control_grid, 0, 0) self.layout.put(self.title, action_bar_width, 0) self.layout.put(self.content, action_bar_width, self.title_spacing) diff --git a/panels/gcode_macros.py b/panels/gcode_macros.py index cd01319a..bb6f62e9 100644 --- a/panels/gcode_macros.py +++ b/panels/gcode_macros.py @@ -81,28 +81,27 @@ class MacroPanel(ScreenPanel): def load_gcode_macros(self): macros = self._screen.printer.get_gcode_macros() + section_name = "displayed_macros %s" % self._screen.connected_printer + logging.info("Macro section name [%s]" % section_name) + for x in macros: macro = x[12:].strip() if macro in self.loaded_macros: continue - logging.debug("Evaluating '%s' value '%s'" % (macro.strip().lower(), - self._config.get_config().getboolean("displayed_macros", macro.lower(), fallback=True))) - - if ("displayed_macros" not in self._config.get_config().sections() or - self._config.get_config().getboolean("displayed_macros", macro.lower(), fallback=True)): + if (section_name not in self._config.get_config().sections() or + self._config.get_config().getboolean(section_name, macro.lower(), fallback=True)): self.add_gcode_macro(macro) def run_gcode_macro(self, widget, macro): self._screen._ws.klippy.gcode_script(macro) def unload_gcode_macros(self): + section_name = "displayed_macros %s" % self._screen.connected_printer for macro in self.loaded_macros: - logging.debug("Evaluating '%s' value '%s'" % (macro.strip().lower(), - self._config.get_config().getboolean("displayed_macros", macro.lower(), fallback=True))) - if ("displayed_macros" in self._config.get_config().sections() and - not self._config.get_config().getboolean("displayed_macros", macro.lower(), fallback=True)): + if (section_name in self._config.get_config().sections() and + not self._config.get_config().getboolean(section_name, macro.lower(), fallback=True)): macros = sorted(self.macros) pos = macros.index(macro) self.labels['macros'].remove_row(pos) diff --git a/panels/job_status.py b/panels/job_status.py index ae78e82a..91f2e3bf 100644 --- a/panels/job_status.py +++ b/panels/job_status.py @@ -18,16 +18,16 @@ class JobStatusPanel(ScreenPanel): progress = 0 state = "printing" + def __init__(self, screen, title, back=False): + super().__init__(screen, title, False) + def initialize(self, panel_name): _ = self.lang.gettext - self.layout = Gtk.Layout() - self.layout.set_size(self._screen.width, self._screen.height) self.timeleft_type = "file" self.create_buttons() grid = self._gtk.HomogeneousGrid() - grid.set_size_request(self._screen.width, self._screen.height) grid.set_row_homogeneous(False) self.labels['button_grid'] = self._gtk.HomogeneousGrid() @@ -170,23 +170,27 @@ class JobStatusPanel(ScreenPanel): sfe_grid.attach(fan_box, 2, 0, 1, 1) self.labels['sfe_grid'] = sfe_grid - self.labels['i1_box'] = Gtk.VBox(spacing=0) + self.labels['i1_box'] = Gtk.HBox(spacing=0) + self.labels['i1_box'].set_vexpand(True) self.labels['i1_box'].get_style_context().add_class("printing-info-box") self.labels['i1_box'].set_valign(Gtk.Align.CENTER) self.labels['i2_box'] = Gtk.VBox(spacing=0) self.labels['i2_box'].set_vexpand(True) self.labels['i2_box'].get_style_context().add_class("printing-info-box") + self.labels['i2_box'].set_valign(Gtk.Align.CENTER) + self.labels['info_grid'] = self._gtk.HomogeneousGrid() + self.labels['info_grid'].attach(self.labels['i1_box'], 0, 0, 1, 1) + self.labels['info_grid'].attach(self.labels['i2_box'], 1, 0, 1, 1) grid.attach(overlay, 0, 0, 1, 1) grid.attach(fi_box, 1, 0, 3, 1) - grid.attach(self.labels['i1_box'], 0, 1, 2, 2) - grid.attach(self.labels['i2_box'], 2, 1, 2, 2) + grid.attach(self.labels['info_grid'], 0, 1, 4, 2) grid.attach(self.labels['button_grid'], 0, 3, 4, 1) self.add_labels() self.grid = grid - self.layout.put(grid, 0, 0) + self.content.add(grid) self._screen.add_subscription(panel_name) @@ -250,8 +254,6 @@ class JobStatusPanel(ScreenPanel): self.labels['cancel'].connect("clicked", self.cancel) self.labels['control'] = self._gtk.ButtonImage("control",_("Control"),"color3") self.labels['control'].connect("clicked", self._screen._go_to_submenu, "") - self.labels['estop'] = self._gtk.ButtonImage("emergency",_("Emergency Stop"),"color4") - self.labels['estop'].connect("clicked", self.emergency_stop) self.labels['menu'] = self._gtk.ButtonImage("complete",_("Main Menu"),"color4") self.labels['menu'].connect("clicked", self.close_panel) self.labels['pause'] = self._gtk.ButtonImage("pause",_("Pause"),"color1" ) @@ -484,12 +486,12 @@ class JobStatusPanel(ScreenPanel): if self.state == "printing": self.labels['button_grid'].attach(self.labels['pause'], 0, 0, 1, 1) self.labels['button_grid'].attach(self.labels['cancel'], 1, 0, 1, 1) - self.labels['button_grid'].attach(self.labels['estop'], 2, 0, 1, 1) + self.labels['button_grid'].attach(Gtk.Label(""), 2, 0, 1, 1) self.labels['button_grid'].attach(self.labels['control'], 3, 0, 1, 1) elif self.state == "paused": self.labels['button_grid'].attach(self.labels['resume'], 0, 0, 1, 1) self.labels['button_grid'].attach(self.labels['cancel'], 1, 0, 1, 1) - self.labels['button_grid'].attach(self.labels['estop'], 2, 0, 1, 1) + self.labels['button_grid'].attach(Gtk.Label(""), 2, 0, 1, 1) self.labels['button_grid'].attach(self.labels['control'], 3, 0, 1, 1) elif self.state == "error" or self.state == "complete" or self.state == "cancelled": self.labels['button_grid'].attach(Gtk.Label(""), 0, 0, 1, 1) diff --git a/panels/printer_select.py b/panels/printer_select.py new file mode 100644 index 00000000..21c7de01 --- /dev/null +++ b/panels/printer_select.py @@ -0,0 +1,33 @@ +import gi +import logging + +gi.require_version("Gtk", "3.0") +from gi.repository import Gtk, Gdk, GLib + +from ks_includes.KlippyGcodes import KlippyGcodes +from ks_includes.screen_panel import ScreenPanel + +logger = logging.getLogger("KlipperScreen.PrinterSelect") + +def create_panel(*args): + return PrinterSelect(*args) + +class PrinterSelect(ScreenPanel): + def __init__(self, screen, title, back=True, action_bar=True, printer_name=True): + super().__init__(screen, title, False, False, False) + + def initialize(self, panel_name): + _ = self.lang.gettext + + printers = self._config.get_printers() + + box = Gtk.Box() + self.content.add(box) + + i = 1 + for printer in printers: + name = list(printer)[0] + self.labels[name] = self._gtk.ButtonImage("extruder",name,"color%s" % (i%4)) + self.labels[name].connect("clicked", self._screen.connect_printer_widget, name) + box.add(self.labels[name]) + i += 1 diff --git a/panels/settings.py b/panels/settings.py index f0261bfb..afaf53ce 100644 --- a/panels/settings.py +++ b/panels/settings.py @@ -21,12 +21,23 @@ class SettingsPanel(ScreenPanel): self.labels['main_box'] = self.create_box('main') self.labels['macros_box'] = self.create_box('macros') + printbox = Gtk.Box(spacing=0) + printbox.set_vexpand(False) + self.labels['add_printer_button'] = self._gtk.Button(_("Add Printer"), "color1") + #printbox.add(self.labels['add_printer_button']) + self.labels['printers_box'] = self.create_box('printers', printbox) + options = self._config.get_configurable_options().copy() options.append({"macros": { "name": _("Displayed Macros"), "type": "menu", "menu": "macros"} }) + options.append({"printers": { + "name": _("Printer Connections"), + "type": "menu", + "menu": "printers" + }}) for option in options: name = list(option)[0] @@ -36,14 +47,25 @@ class SettingsPanel(ScreenPanel): macro = macro[12:] self.macros[macro] = { "name": macro, - "section": "displayed_macros", + "section": "displayed_macros %s" % self._screen.connected_printer, "type": "macro" } for macro in list(self.macros): self.add_option('macros', self.macros, macro, self.macros[macro]) - logging.debug("Macros: %s" % self.macros) + self.printers = {} + for printer in self._config.get_printers(): + logging.debug("Printer: %s" % printer) + pname = list(printer)[0] + self.printers[pname] = { + "name": pname, + "section": "printer %s" % pname, + "type": "printer", + "moonraker_host": printer[pname]['moonraker_host'], + "moonraker_port": printer[pname]['moonraker_port'], + } + self.add_option("printers", self.printers, pname, self.printers[pname]) self.control['back'].disconnect_by_func(self._screen._menu_go_back) self.control['back'].connect("clicked", self.back) @@ -59,7 +81,7 @@ class SettingsPanel(ScreenPanel): else: self._screen._menu_go_back() - def create_box(self, name): + def create_box(self, name, insert=None): # Create a scroll window for the macros scroll = Gtk.ScrolledWindow() scroll.set_property("overlay-scrolling", False) @@ -72,6 +94,8 @@ class SettingsPanel(ScreenPanel): # Create a box to contain all of the above box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) box.set_vexpand(True) + if insert is not None: + box.pack_start(insert, False, False, 0) box.pack_start(scroll, True, True, 0) return box @@ -135,6 +159,15 @@ class SettingsPanel(ScreenPanel): dropdown.set_entry_text_column(0) dev.add(dropdown) logging.debug("Children: %s" % dropdown.get_children()) + elif option['type'] == "printer": + logging.debug("Option: %s" % option) + box = Gtk.Box() + box.set_vexpand(False) + label = Gtk.Label() + url = "%s:%s" % (option['moonraker_host'], option['moonraker_port']) + label.set_markup("%s\n%s" % (option['name'], url)) + box.add(label) + dev.add(box) elif option['type'] == "menu": open = self._gtk.ButtonImage("open",None,"color3") open.connect("clicked", self.load_menu, option['menu']) diff --git a/panels/splash_screen.py b/panels/splash_screen.py index 5ea43213..af56eee9 100644 --- a/panels/splash_screen.py +++ b/panels/splash_screen.py @@ -61,9 +61,11 @@ class SplashScreenPanel(ScreenPanel): _ = self.lang.gettext if "firmware_restart" not in self.labels: + self.labels['printer_select'] = self._gtk.ButtonImage("shuffle",_("Change Printer")) + self.labels['printer_select'].connect("clicked", self._screen.show_printer_select) self.labels['menu'] = self._gtk.ButtonImage("control",_("Menu"),"color4") self.labels['menu'].connect("clicked", self._screen._go_to_submenu, "") - self.labels['power'] = self._gtk.ButtonImage("reboot",_("Power On Printer"),"color3") + self.labels['power'] = self._gtk.ButtonImage("shutdown",_("Power On Printer"),"color3") self.labels['restart'] = self._gtk.ButtonImage("reboot",_("Restart"),"color1") self.labels['restart'].connect("clicked", self.restart) self.labels['firmware_restart'] = self._gtk.ButtonImage("restart",_("Firmware Restart"),"color2") @@ -82,7 +84,8 @@ class SplashScreenPanel(ScreenPanel): self.labels['actions'].add(self.labels['restart']) self.labels['actions'].add(self.labels['firmware_restart']) self.labels['actions'].add(self.labels['menu']) - self.labels['actions'].show() + self.labels['actions'].add(self.labels['printer_select']) + self.labels['actions'].show_all() def firmware_restart(self, widget): self._screen._ws.klippy.restart_firmware() diff --git a/screen.py b/screen.py index 901193ae..442ac14b 100644 --- a/screen.py +++ b/screen.py @@ -54,6 +54,7 @@ class KlipperScreen(Gtk.Window): """ Class for creating a screen for Klipper via HDMI """ _cur_panels = [] bed_temp_label = None + connecting = False connected_printer = None currentPanel = None files = None @@ -66,6 +67,8 @@ class KlipperScreen(Gtk.Window): panels = {} popup_message = None printer = None + printer_select_callbacks = [] + printer_select_prepanel = None rtl_languages = ['he_il'] subscriptions = [] shutdown = True @@ -116,22 +119,51 @@ class KlipperScreen(Gtk.Window): self.get_window().set_cursor(Gdk.Cursor(Gdk.CursorType.ARROW)) else: self.get_window().set_cursor(Gdk.Cursor(Gdk.CursorType.BLANK_CURSOR)) - pname = list(self._config.get_printers()[0])[0] - self.connect_printer(pname, self._config.get_printers()[0][pname]) - def connect_printer(self, name, data): + printers = self._config.get_printers() + logging.debug("Printers: %s" % printers) + if len(printers) == 1: + pname = list(self._config.get_printers()[0])[0] + self.connect_printer(pname) + else: + self.show_panel("printer_select","printer_select","Printer Select", 2) + + def connect_printer_widget(self, widget, name): + self.connect_printer(name) + + def connect_printer(self, name): _ = self.lang.gettext if self.connected_printer == name: + if self.printer_select_prepanel != None: + self.show_panel(self.printer_select_prepanel, "","", 2) + self.printer_select_prepanel = None + while len(self.printer_select_callbacks) > 0: + i = self.printer_select_callbacks.pop(0) + i() return + self.printer_select_callbacks = [] + self.printer_select_prepanel = None + if self.files is not None: self.files.stop() + self.files = None + + for printer in self._config.get_printers(): + pname = list(printer)[0] + + if pname != name: + continue + data = printer[pname] + break + + if self._ws is not None: + self._ws.close() + self.connecting = True logging.info("Connecting to printer: %s" % name) - self.apiclient = KlippyRest(self._config.get_main_config_option("moonraker_host"), - self._config.get_main_config_option("moonraker_port"), - self._config.get_main_config_option("moonraker_api_key", False)) + self.apiclient = KlippyRest(data["moonraker_host"], data["moonraker_port"], data["moonraker_api_key"]) self.printer = Printer({ "software_version": "Unknown" @@ -149,6 +181,8 @@ class KlipperScreen(Gtk.Window): self._remove_all_panels() panels = list(self.panels) + if len(self.subscriptions) > 0: + self.subscriptions = [] for panel in panels: del self.panels[panel] self.printer_initializing(_("Connecting to %s") % name) @@ -169,19 +203,17 @@ class KlipperScreen(Gtk.Window): self.printer.configure_power_devices(powerdevs['result']) self.panels['splash_screen'].show_restart_buttons() - if self._ws is not None: - self._ws.close - self._ws = KlippyWebsocket(self, { "on_connect": self.init_printer, "on_message": self._websocket_callback, "on_close": self.printer_initializing }, - self._config.get_main_config_option("moonraker_host"), - self._config.get_main_config_option("moonraker_port") + data["moonraker_host"], + data["moonraker_port"] ) self._ws.initial_connect() + self.connecting = False self.files = KlippyFiles(self) self.files.start() @@ -458,12 +490,25 @@ class KlipperScreen(Gtk.Window): if self.dpms_timeout == None and functions.dpms_loaded == True: self.dpms_timeout = GLib.timeout_add(1000, self.check_dpms_state) + def show_printer_select(self, widget=None): + logging.debug("Saving panel: %s" % self._cur_panels[0]) + self.printer_select_prepanel = self._cur_panels[0] + self.show_panel("printer_select","printer_select","Printer Select", 2) + def state_disconnected(self): + if "printer_select" in self._cur_panels: + self.printer_select_callbacks = [self.state_disconnected] + return + _ = self.lang.gettext logging.debug("### Going to disconnected") self.printer_initializing(_("Klipper has disconnected")) def state_error(self): + if "printer_select" in self._cur_panels: + self.printer_select_callbacks = [self.state_error] + return + _ = self.lang.gettext msg = self.printer.get_stat("webhooks","state_message") if "FIRMWARE_RESTART" in msg: @@ -484,26 +529,45 @@ class KlipperScreen(Gtk.Window): self.printer_printing() def state_printing(self): + if "printer_select" in self._cur_panels: + self.printer_select_callbacks = [self.state_printing] + return + if "job_status" not in self._cur_panels: self.printer_printing() def state_ready(self): + if "printer_select" in self._cur_panels: + self.printer_select_callbacks = [self.state_ready] + return + # 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): + if "printer_select" in self._cur_panels: + self.printer_select_callbacks = [self.state_startup] + return + _ = self.lang.gettext self.printer_initializing(_("Klipper is attempting to start")) def state_shutdown(self): + if "printer_select" in self._cur_panels: + self.printer_select_callbacks = [self.state_shutdown] + return + _ = self.lang.gettext self.printer_initializing(_("Klipper has shutdown")) def _websocket_callback(self, action, data): _ = self.lang.gettext + if self.connecting == True: + return + if action == "notify_klippy_disconnected": logging.debug("Received notify_klippy_disconnected") self.printer.change_state("disconnected") @@ -592,10 +656,11 @@ class KlipperScreen(Gtk.Window): self.printer.configure_power_devices(powerdevs['result']) def printer_ready(self): + _ = self.lang.gettext self.close_popup_message() # Force update to printer webhooks state in case the update is missed due to websocket subscribe not yet sent self.printer.process_update({"webhooks":{"state":"ready","state_message": "Printer is ready"}}) - self.show_panel('main_panel', "main_menu", self.connected_printer, 2, + self.show_panel('main_panel', "main_menu", _("Home"), 2, items=self._config.get_menu_items("__main"), extrudercount=self.printer.get_extruder_count()) self.ws_subscribe() if "job_status" in self.panels: diff --git a/styles/z-bolt/images/shuffle.svg b/styles/z-bolt/images/shuffle.svg new file mode 100644 index 00000000..36e01042 --- /dev/null +++ b/styles/z-bolt/images/shuffle.svg @@ -0,0 +1,5 @@ + + + + +