diff options
Diffstat (limited to 'klippy/extras/virtual_sdcard.py')
-rw-r--r-- | klippy/extras/virtual_sdcard.py | 138 |
1 files changed, 90 insertions, 48 deletions
diff --git a/klippy/extras/virtual_sdcard.py b/klippy/extras/virtual_sdcard.py index 6dc49e2f..aca257ea 100644 --- a/klippy/extras/virtual_sdcard.py +++ b/klippy/extras/virtual_sdcard.py @@ -5,7 +5,7 @@ # This file may be distributed under the terms of the GNU GPLv3 license. import os, sys, logging, io -VALID_GCODE_EXTS = ['gcode', 'g', 'gco'] +VALID_GCODE_EXTS = ["gcode", "g", "gco"] DEFAULT_ERROR_GCODE = """ {% if 'heaters' in printer %} @@ -13,39 +13,45 @@ DEFAULT_ERROR_GCODE = """ {% endif %} """ + class VirtualSD: def __init__(self, config): self.printer = config.get_printer() - self.printer.register_event_handler("klippy:shutdown", - self.handle_shutdown) + self.printer.register_event_handler("klippy:shutdown", self.handle_shutdown) # sdcard state - sd = config.get('path') + sd = config.get("path") self.sdcard_dirname = os.path.normpath(os.path.expanduser(sd)) self.current_file = None self.file_position = self.file_size = 0 # Print Stat Tracking - self.print_stats = self.printer.load_object(config, 'print_stats') + self.print_stats = self.printer.load_object(config, "print_stats") # Work timer self.reactor = self.printer.get_reactor() self.must_pause_work = self.cmd_from_sd = False self.next_file_position = 0 self.work_timer = None # Error handling - gcode_macro = self.printer.load_object(config, 'gcode_macro') + gcode_macro = self.printer.load_object(config, "gcode_macro") self.on_error_gcode = gcode_macro.load_template( - config, 'on_error_gcode', DEFAULT_ERROR_GCODE) + config, "on_error_gcode", DEFAULT_ERROR_GCODE + ) # Register commands - self.gcode = self.printer.lookup_object('gcode') - for cmd in ['M20', 'M21', 'M23', 'M24', 'M25', 'M26', 'M27']: - self.gcode.register_command(cmd, getattr(self, 'cmd_' + cmd)) - for cmd in ['M28', 'M29', 'M30']: + self.gcode = self.printer.lookup_object("gcode") + for cmd in ["M20", "M21", "M23", "M24", "M25", "M26", "M27"]: + self.gcode.register_command(cmd, getattr(self, "cmd_" + cmd)) + for cmd in ["M28", "M29", "M30"]: self.gcode.register_command(cmd, self.cmd_error) self.gcode.register_command( - "SDCARD_RESET_FILE", self.cmd_SDCARD_RESET_FILE, - desc=self.cmd_SDCARD_RESET_FILE_help) + "SDCARD_RESET_FILE", + self.cmd_SDCARD_RESET_FILE, + desc=self.cmd_SDCARD_RESET_FILE_help, + ) self.gcode.register_command( - "SDCARD_PRINT_FILE", self.cmd_SDCARD_PRINT_FILE, - desc=self.cmd_SDCARD_PRINT_FILE_help) + "SDCARD_PRINT_FILE", + self.cmd_SDCARD_PRINT_FILE, + desc=self.cmd_SDCARD_PRINT_FILE_help, + ) + def handle_shutdown(self): if self.work_timer is not None: self.must_pause_work = True @@ -57,24 +63,29 @@ class VirtualSD: except: logging.exception("virtual_sdcard shutdown read") return - logging.info("Virtual sdcard (%d): %s\nUpcoming (%d): %s", - readpos, repr(data[:readcount]), - self.file_position, repr(data[readcount:])) + logging.info( + "Virtual sdcard (%d): %s\nUpcoming (%d): %s", + readpos, + repr(data[:readcount]), + self.file_position, + repr(data[readcount:]), + ) + def stats(self, eventtime): if self.work_timer is None: return False, "" return True, "sd_pos=%d" % (self.file_position,) + def get_file_list(self, check_subdirs=False): if check_subdirs: flist = [] - for root, dirs, files in os.walk( - self.sdcard_dirname, followlinks=True): + for root, dirs, files in os.walk(self.sdcard_dirname, followlinks=True): for name in files: - ext = name[name.rfind('.')+1:] + ext = name[name.rfind(".") + 1 :] if ext not in VALID_GCODE_EXTS: continue full_path = os.path.join(root, name) - r_path = full_path[len(self.sdcard_dirname) + 1:] + r_path = full_path[len(self.sdcard_dirname) + 1 :] size = os.path.getsize(full_path) flist.append((r_path, size)) return sorted(flist, key=lambda f: f[0].lower()) @@ -82,43 +93,53 @@ class VirtualSD: dname = self.sdcard_dirname try: filenames = os.listdir(self.sdcard_dirname) - return [(fname, os.path.getsize(os.path.join(dname, fname))) - for fname in sorted(filenames, key=str.lower) - if not fname.startswith('.') - and os.path.isfile((os.path.join(dname, fname)))] + return [ + (fname, os.path.getsize(os.path.join(dname, fname))) + for fname in sorted(filenames, key=str.lower) + if not fname.startswith(".") + and os.path.isfile((os.path.join(dname, fname))) + ] except: logging.exception("virtual_sdcard get_file_list") raise self.gcode.error("Unable to get file list") + def get_status(self, eventtime): return { - 'file_path': self.file_path(), - 'progress': self.progress(), - 'is_active': self.is_active(), - 'file_position': self.file_position, - 'file_size': self.file_size, + "file_path": self.file_path(), + "progress": self.progress(), + "is_active": self.is_active(), + "file_position": self.file_position, + "file_size": self.file_size, } + def file_path(self): if self.current_file: return self.current_file.name return None + def progress(self): if self.file_size: return float(self.file_position) / self.file_size else: - return 0. + return 0.0 + def is_active(self): return self.work_timer is not None + def do_pause(self): if self.work_timer is not None: self.must_pause_work = True while self.work_timer is not None and not self.cmd_from_sd: - self.reactor.pause(self.reactor.monotonic() + .001) + self.reactor.pause(self.reactor.monotonic() + 0.001) + def do_resume(self): if self.work_timer is not None: raise self.gcode.error("SD busy") self.must_pause_work = False self.work_timer = self.reactor.register_timer( - self.work_handler, self.reactor.NOW) + self.work_handler, self.reactor.NOW + ) + def do_cancel(self): if self.current_file is not None: self.do_pause() @@ -126,9 +147,11 @@ class VirtualSD: self.current_file = None self.print_stats.note_cancel() self.file_position = self.file_size = 0 + # G-Code commands def cmd_error(self, gcmd): raise gcmd.error("SD write not supported") + def _reset_file(self): if self.current_file is not None: self.do_pause() @@ -137,24 +160,30 @@ class VirtualSD: self.file_position = self.file_size = 0 self.print_stats.reset() self.printer.send_event("virtual_sdcard:reset_file") - cmd_SDCARD_RESET_FILE_help = "Clears a loaded SD File. Stops the print "\ - "if necessary" + + cmd_SDCARD_RESET_FILE_help = ( + "Clears a loaded SD File. Stops the print " "if necessary" + ) + def cmd_SDCARD_RESET_FILE(self, gcmd): if self.cmd_from_sd: - raise gcmd.error( - "SDCARD_RESET_FILE cannot be run from the sdcard") + raise gcmd.error("SDCARD_RESET_FILE cannot be run from the sdcard") self._reset_file() - cmd_SDCARD_PRINT_FILE_help = "Loads a SD file and starts the print. May "\ - "include files in subdirectories." + + cmd_SDCARD_PRINT_FILE_help = ( + "Loads a SD file and starts the print. May " "include files in subdirectories." + ) + def cmd_SDCARD_PRINT_FILE(self, gcmd): if self.work_timer is not None: raise gcmd.error("SD busy") self._reset_file() filename = gcmd.get("FILENAME") - if filename[0] == '/': + if filename[0] == "/": filename = filename[1:] self._load_file(gcmd, filename, check_subdirs=True) self.do_resume() + def cmd_M20(self, gcmd): # List SD card files = self.get_file_list() @@ -162,28 +191,31 @@ class VirtualSD: for fname, fsize in files: gcmd.respond_raw("%s %d" % (fname, fsize)) gcmd.respond_raw("End file list") + def cmd_M21(self, gcmd): # Initialize SD card gcmd.respond_raw("SD card ok") + def cmd_M23(self, gcmd): # Select SD file if self.work_timer is not None: raise gcmd.error("SD busy") self._reset_file() filename = gcmd.get_raw_command_parameters().strip() - if filename.startswith('/'): + if filename.startswith("/"): filename = filename[1:] self._load_file(gcmd, filename) + def _load_file(self, gcmd, filename, check_subdirs=False): files = self.get_file_list(check_subdirs) flist = [f[0] for f in files] - files_by_lower = { fname.lower(): fname for fname, fsize in files } + files_by_lower = {fname.lower(): fname for fname, fsize in files} fname = filename try: if fname not in flist: fname = files_by_lower[fname.lower()] fname = os.path.join(self.sdcard_dirname, fname) - f = io.open(fname, 'r', newline='') + f = io.open(fname, "r", newline="") f.seek(0, os.SEEK_END) fsize = f.tell() f.seek(0) @@ -196,31 +228,40 @@ class VirtualSD: self.file_position = 0 self.file_size = fsize self.print_stats.set_current_file(filename) + def cmd_M24(self, gcmd): # Start/resume SD print self.do_resume() + def cmd_M25(self, gcmd): # Pause SD print self.do_pause() + def cmd_M26(self, gcmd): # Set SD position if self.work_timer is not None: raise gcmd.error("SD busy") - pos = gcmd.get_int('S', minval=0) + pos = gcmd.get_int("S", minval=0) self.file_position = pos + def cmd_M27(self, gcmd): # Report SD print status if self.current_file is None: gcmd.respond_raw("Not SD printing.") return - gcmd.respond_raw("SD printing byte %d/%d" - % (self.file_position, self.file_size)) + gcmd.respond_raw( + "SD printing byte %d/%d" % (self.file_position, self.file_size) + ) + def get_file_position(self): return self.next_file_position + def set_file_position(self, pos): self.next_file_position = pos + def is_cmd_from_sd(self): return self.cmd_from_sd + # Background work timer def work_handler(self, eventtime): logging.info("Starting SD card print (position %d)", self.file_position) @@ -251,7 +292,7 @@ class VirtualSD: logging.info("Finished SD card print") self.gcode.respond_raw("Done printing file") break - lines = data.split('\n') + lines = data.split("\n") lines[0] = partial_input + lines[0] partial_input = lines.pop() lines.reverse() @@ -304,5 +345,6 @@ class VirtualSD: self.print_stats.note_complete() return self.reactor.NEVER + def load_config(config): return VirtualSD(config) |