From 5e31e3cfa6c6dcdf1d113470f9540b8f57520a9e Mon Sep 17 00:00:00 2001
From: jordanruthe <31575189+jordanruthe@users.noreply.github.com>
Date: Fri, 5 Mar 2021 18:30:59 -0500
Subject: [PATCH] Multiple printers (#85)
* screen/printer_select/splash_screen: Updates to allow changing between moonraker instances
* Updates to multiple printers
* settings: Display printer endpoints
* Update macros to be on a per-printer basis
* files: Changes to clear out file cache on printer switch
* job_status: Redo page for action bar
* splash_screen: Change icon
* websocket: Actually close the websocket
* printer: Fix error case
* splash_screen: show buttons update
* readme update
---
README.md | 25 ++++-----
docs/changelog.md | 3 ++
ks_includes/KlippyWebsocket.py | 1 +
ks_includes/config.py | 22 ++++----
ks_includes/files.py | 24 ++++-----
ks_includes/printer.py | 6 +++
ks_includes/screen_panel.py | 62 ++++++++++++----------
panels/gcode_macros.py | 17 +++---
panels/job_status.py | 24 +++++----
panels/printer_select.py | 33 ++++++++++++
panels/settings.py | 39 ++++++++++++--
panels/splash_screen.py | 7 ++-
screen.py | 89 +++++++++++++++++++++++++++-----
styles/z-bolt/images/shuffle.svg | 5 ++
14 files changed, 253 insertions(+), 104 deletions(-)
create mode 100644 panels/printer_select.py
create mode 100644 styles/z-bolt/images/shuffle.svg
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

+
+
+### 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 @@
+
+
+