aboutsummaryrefslogtreecommitdiffstats
path: root/klippy/extras/virtual_sdcard.py
diff options
context:
space:
mode:
Diffstat (limited to 'klippy/extras/virtual_sdcard.py')
-rw-r--r--klippy/extras/virtual_sdcard.py138
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)