Files
Ender-3_V3_KE_Klipper/klippy/extras/virtual_sdcard.py
T
2024-01-09 17:33:02 +08:00

880 lines
44 KiB
Python
Executable File

# Virtual sdcard support (print files directly from a host g-code file)
#
# Copyright (C) 2018 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import os, logging, io, json, time
from .tool import reportInformation
VALID_GCODE_EXTS = ['gcode', 'g', 'gco']
LAYER_KEYS = [";LAYER:", "; layer:", "; LAYER:", ";AFTER_LAYER_CHANGE", ";LAYER_CHANGE"]
class VirtualSD:
def __init__(self, config):
self.printer = config.get_printer()
self.printer.register_event_handler("klippy:shutdown",
self.handle_shutdown)
# sdcard state
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')
# 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')
self.on_error_gcode = gcode_macro.load_template(
config, 'on_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.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)
self.gcode.register_command(
"SDCARD_PRINT_FILE", self.cmd_SDCARD_PRINT_FILE,
desc=self.cmd_SDCARD_PRINT_FILE_help)
self.count_G1 = 0
self.count_line = 0
self.do_resume_status = False
self.eepromWriteCount = 1
self.fan_state = ""
self.toolhead_moved = False
self.gcode_layer_path = "/usr/data/creality/userdata/config/gcode_layer.json"
self.user_print_refer_path = "/usr/data/creality/userdata/config/user_print_refer.json"
self.print_file_name_path = "/usr/data/creality/userdata/config/print_file_name.json"
self.print_first_layer = False
self.first_layer_stop = False
self.count_M204 = 0
self.layer = 0
self.layer_count = 0
self.is_continue_print = False
self.slow_print = False
self.slow_count = 0
self.speed_factor = 1.0/60.0
self.run_dis = 0.0
self.print_id = ""
self.cur_print_data = {}
def handle_shutdown(self):
if self.work_timer is not None:
self.must_pause_work = True
try:
readpos = max(self.file_position - 1024, 0)
readcount = self.file_position - readpos
self.current_file.seek(readpos)
data = self.current_file.read(readcount + 128)
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:]))
self.print_first_layer = False
self.first_layer_stop = False
self.print_stats.power_loss = 0
self.count_M204 = 0
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 name in files:
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:]
size = os.path.getsize(full_path)
flist.append((r_path, size))
return sorted(flist, key=lambda f: f[0].lower())
else:
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)))]
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,
'first_layer_stop': self.first_layer_stop,
'layer': self.layer,
'layer_count': self.layer_count,
'run_dis': self.run_dis
}
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.
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)
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)
def do_cancel(self):
self.print_stats.power_loss = 0
self.first_layer_stop = False
self.print_first_layer = False
self.count_M204 = 0
self.layer = 0
self.layer_count = 0
self.resume_print_speed()
if self.current_file is not None:
self.do_pause()
self.current_file.close()
self.current_file = None
self.print_stats.note_cancel()
self.file_position = self.file_size = 0.
from subprocess import call
if os.path.exists(self.print_file_name_path):
os.remove(self.print_file_name_path)
if os.path.exists(self.gcode.exclude_object_info):
os.remove(self.gcode.exclude_object_info)
call("sync", shell=True)
try:
power_loss_switch = False
if os.path.exists(self.user_print_refer_path):
with open(self.user_print_refer_path, "r") as f:
data = json.loads(f.read())
power_loss_switch = data.get("power_loss", {}).get("switch", False)
bl24c16f = self.printer.lookup_object('bl24c16f') if "bl24c16f" in self.printer.objects else None
if power_loss_switch and bl24c16f:
bl24c16f.setEepromDisable()
# self.gcode.run_script("EEPROM_WRITE_BYTE ADDR=1 VAL=255")
except Exception as err:
pass
self.update_print_history_info(only_update_status=True, state="cancelled")
if self.print_id and self.cur_print_data:
reportInformation("key701", data=self.cur_print_data)
self.print_id = ""
self.cur_print_data = {}
# 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()
self.current_file.close()
self.current_file = None
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"
def cmd_SDCARD_RESET_FILE(self, gcmd):
if self.cmd_from_sd:
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."
def cmd_SDCARD_PRINT_FILE(self, gcmd):
self.print_id = ""
if self.work_timer is not None:
raise gcmd.error("SD busy")
self._reset_file()
filename = gcmd.get("FILENAME")
self.is_continue_print = gcmd.get("ISCONTINUEPRINT", False)
self.rm_power_loss_info()
first_floor = gcmd.get("FIRST_FLOOR_PRINT", None)
if first_floor is None or first_floor == False:
self.print_first_layer = False
else:
self.print_first_layer = True
if filename[0] == '/':
filename = filename[1:]
self._load_file(gcmd, filename, check_subdirs=True)
self.do_resume()
self.record_print_history(str(self.current_file.name))
def record_print_history(self, file_path=""):
try:
if os.path.exists(file_path):
dir_path = os.path.dirname(file_path)
file_name = os.path.basename(file_path)
metadata_info = self.get_print_file_metadata(filename=file_name, filepath=dir_path)
start_time = time.time()
self.print_id = str(start_time)
metadata = metadata_info.get("metadata", {})
if metadata_info.get("metadata", {}) and metadata_info.get("metadata", {}).get("filament_type"):
metadata["model_info"]["MaterialName"] = metadata_info.get("metadata", {})["filament_type"]
data = {
"end_time": start_time,
"filament_used": 0,
"filename": file_name,
"metadata": metadata,
"print_duration": 0,
"start_time": start_time,
"status": "in_progress",
"total_duration": 0,
}
result = {"count": 1, "jobs": [data]}
self.cur_print_data = result
return
except Exception as err:
logging.error(err)
def update_print_history_info(self, only_update_status=False, state="", error_msg=""):
if self.print_id:
ret = {}
try:
update_obj = None
index = -1
ret = self.cur_print_data
if ret and ret.get("jobs", []):
print_list = ret.get("jobs", [])
for obj in print_list:
if obj.get("start_time", "") and str(obj.get("start_time", "")) == self.print_id:
index = print_list.index(obj)
update_obj = obj
if not only_update_status:
update_obj["filament_used"] = self.print_stats.filament_used
update_obj["print_duration"] = self.print_stats.print_duration
update_obj["total_duration"] = self.print_stats.total_duration
update_obj["end_time"] = time.time()
if not state:
state = "in_progress"
if error_msg:
update_obj["error_msg"] = error_msg
update_obj["status"] = state
if only_update_status and self.print_id and (state == "error" or state == "completed") and os.path.exists("/tmp/camera_main"):
update_obj["jpg_filename"] = "%s.jpg" % self.print_id
time.sleep(0.5)
reportInformation("key608", data={"print_id": self.print_id})
if index != -1:
print_list[index] = update_obj
ret["jobs"] = print_list
self.cur_print_data = ret
except Exception as err:
logging.error(err)
def rm_power_loss_info(self):
if not self.is_continue_print and os.path.exists(self.print_file_name_path):
try:
power_loss_switch = False
with open(self.user_print_refer_path, "r") as f:
data = json.loads(f.read())
power_loss_switch = data.get("power_loss", {}).get("switch", False)
bl24c16f = self.printer.lookup_object('bl24c16f') if "bl24c16f" in self.printer.objects and power_loss_switch else None
if power_loss_switch and bl24c16f:
os.remove(self.print_file_name_path)
if os.path.exists(self.gcode.exclude_object_info):
os.remove(self.gcode.exclude_object_info)
self.gcode.run_script_from_command("EEPROM_WRITE_BYTE ADDR=1 VAL=255")
logging.info("rm power_loss info success")
except Exception as err:
logging.error("rm power_loss info fail, err:%s" % err)
def cmd_M20(self, gcmd):
# List SD card
files = self.get_file_list()
gcmd.respond_raw("Begin file list")
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('/'):
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 }
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.seek(0, os.SEEK_END)
fsize = f.tell()
f.seek(0)
except:
logging.exception("virtual_sdcard file open")
raise gcmd.error("""{"code":"key121", "msg": "Unable to open file", "values": []}""")
gcmd.respond_raw("File opened:%s Size:%d" % (filename, fsize))
gcmd.respond_raw("File selected")
self.current_file = f
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)
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))
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
def tail_read(self, f):
cur_pos = f.tell()
buf = ''
while True:
b = str(f.read(1))
buf = b + buf
cur_pos -= 1
if cur_pos < 0: break
f.seek(cur_pos)
if b.startswith("\n") or b.startswith("\r"):
buf = '\n'
if (buf.startswith("G1") or buf.startswith("G0") or buf.startswith(";")) and buf.endswith("\n"):
break
return buf
def getXYZE(self, file_path, file_position):
result = {"X": 0, "Y": 0, "Z": 0, "E": 0}
try:
import io
with io.open(file_path, "r", encoding="utf-8") as f:
f.seek(file_position)
while True:
cur_pos = f.tell()
if cur_pos<=0:
break
line = self.tail_read(f)
line_list = line.split(" ")
if not result["E"] and "E" in line:
for obj in line_list:
if obj.startswith("E"):
ret = obj[1:].split("\r")[0]
ret = ret.split("\n")[0]
if ret.startswith("."):
result["E"] = float(("0"+ret.strip(" ")))
else:
result["E"] = float(ret.strip(" "))
if not result["X"] and not result["Y"]:
for obj in line_list:
if obj.startswith("X"):
logging.info("power_loss getXYZE X:%s" % obj)
result["X"] = float(obj.split("\r")[0][1:])
if obj.startswith("Y"):
logging.info("power_loss getXYZE Y:%s" % obj)
result["Y"] = float(obj.split("\r")[0][1:])
if not result["Z"] and "Z" in line:
for obj in line_list:
if obj.startswith("Z"):
logging.info("power_loss getXYZE Z:%s" % obj)
result["Z"] = float(obj.split("\r")[0][1:])
if result["X"] and result["Y"] and result["Z"] and result["E"]:
logging.info("get XYZE:%s" % str(result))
logging.info("power_loss get XYZE:%s" % str(result))
break
self.reactor.pause(self.reactor.monotonic() + .001)
except UnicodeDecodeError as err:
logging.exception(err)
# UnicodeDecodeError 'utf-8' codec can't decode byte 0xff in postion 5278: invalid start byte
err_msg = '{"code": "key572", "msg": "File UnicodeDecodeError"}'
self.gcode.respond_info(err_msg)
raise self.printer.command_error(err_msg)
except Exception as err:
logging.exception(err)
return result
def get_print_temperature(self, file_path):
bed = 0
extruder = 202.0
if os.path.exists(self.gcode.last_temperature_info):
try:
with open(self.gcode.last_temperature_info, "r") as f:
result = f.read()
if len(result) > 0:
result = json.loads(result)
bed = float(result.get("bed", 0))
extruder = float(result.get("extruder", 201.0))
except Exception as err:
logging.error("get_print_temperature: %s" % err)
logging.info("power_loss get_print_temperature: bed:%s, extruder:%s" % (bed, extruder))
return bed, extruder
def record_layer(self, layer):
"""
record current print file layer
"""
with open(self.gcode_layer_path, "w") as f:
f.write(json.dumps({"layer": layer}))
f.flush()
def get_layer(self):
"""
get last print file layer
"""
layer = 0
if os.path.exists(self.gcode_layer_path):
try:
with open(self.gcode_layer_path, "r") as f:
layer = int(json.loads(f.read()).get("layer"))
except Exception as err:
logging.error(err)
os.remove(self.gcode_layer_path)
return layer
def get_print_file_metadata(self, filename, filepath="/usr/data/printer_data/gcodes"):
from subprocess import check_output
result = {}
python_env = "/usr/share/klippy-env/bin/python3"
# -f gcode filename -p gcode file dir
cmd = "%s /usr/share/klipper/klippy/extras/metadata.py -f '%s' -p %s" % (python_env, filename, filepath)
try:
result = json.loads(check_output(cmd, shell=True).decode("utf-8"))
except Exception as err:
logging.error(err)
return result
def get_file_layer_count(self, filename):
filename = filename.split("/")[-1]
import math
layer_count = 0
result = self.get_print_file_metadata(filename)
if not result:
return layer_count
try:
layer_count = result.get("metadata").get("layer_count", 0)
first_layer_height = result.get("metadata").get("first_layer_height", 0)
object_height = result.get("metadata").get("object_height", 0)
layer_height = result.get("metadata").get("layer_height", 0)
if not layer_count and object_height > 0 and layer_height > 0:
layer_count = math.ceil((object_height - first_layer_height) / layer_height + 1)
except Exception as err:
logging.error(err)
return layer_count
def resume_print_speed(self):
if self.slow_print == True:
self.slow_print = False
self.slow_count = 0
speed_cmd = "M220 S%s" % (self.speed_factor * 100 * 60)
logging.info("power_loss slow_print:%s Resume" % speed_cmd)
self.gcode.run_script_from_command(speed_cmd)
# Background work timer
def work_handler(self, eventtime):
filename = os.path.basename(self.current_file.name) if self.current_file else ""
logging.info("work_handler start print, filename:%s" % self.current_file.name)
# self.print_stats.note_start()
import time
from subprocess import check_output
self.count_line = 0
self.count_G1 = 0
self.eepromWriteCount = 1
gcode_move = self.printer.lookup_object('gcode_move', None)
try:
if os.path.exists(self.user_print_refer_path):
with open(self.user_print_refer_path, "r") as f:
data = json.loads(f.read())
delay_photography_switch = data.get("delay_image", {}).get("switch", 1)
location = data.get("delay_image", {}).get("location", 0)
frame = data.get("delay_image", {}).get("frame", 15)
interval = data.get("delay_image", {}).get("interval", 1)
power_loss_switch = data.get("power_loss", {}).get("switch", False)
except Exception as err:
delay_photography_switch = 1
location = 0
frame = 15
interval = 1
power_loss_switch = False
logging.info("delay_photography status: delay_photography_switch:%s, location:%s, frame:%s, interval:%s" % (
delay_photography_switch, location, frame, interval
))
self.layer_count = self.get_file_layer_count(self.current_file.name)
bl24c16f = self.printer.lookup_object('bl24c16f') if "bl24c16f" in self.printer.objects and power_loss_switch else None
eepromState = True
try:
sameFileName = False
if os.path.exists(self.print_file_name_path):
with open(self.print_file_name_path, "r") as f:
result = (json.loads(f.read()))
if result.get("file_path", "") == self.current_file.name:
sameFileName = True
else:
# clear power_loss info
if os.path.exists(self.gcode.exclude_object_info):
os.remove(self.gcode.exclude_object_info)
os.remove(self.print_file_name_path)
if power_loss_switch and bl24c16f:
bl24c16f.setEepromDisable()
eepromState = bl24c16f.checkEepromFirstEnable() if power_loss_switch and bl24c16f else True
if power_loss_switch and bl24c16f and not self.do_resume_status and sameFileName and not eepromState and self.is_continue_print:
with self.gcode.mutex:
try:
self.print_stats.note_start(info_path=self.print_file_name_path)
self.is_continue_print = False
logging.info("power_loss start do_resume...")
logging.info("power_loss start print, filename:%s" % self.current_file.name)
pos = bl24c16f.eepromReadHeader()
logging.info("power_loss pos:%s" % pos)
print_info = bl24c16f.eepromReadBody(pos)
logging.info("power_loss print_info:%s" % str(print_info))
self.file_position = int(print_info.get("file_position", 0))
logging.info("power_loss file_position:%s" % self.file_position)
self.layer = self.get_layer()
gcode = self.printer.lookup_object('gcode')
temperature = self.get_print_temperature(self.current_file.name)
gcode.run_script_from_command("M140 S%s" % temperature[0])
gcode.run_script_from_command("M109 S%s" % temperature[1])
XYZE = self.getXYZE(self.current_file.name, self.file_position)
logging.info("power_loss XYZE:%s, file_position:%s " % (str(XYZE), self.file_position))
if XYZE.get("Z") == 0:
logging.error("power_loss gcode Z == 0 err")
from subprocess import call
if os.path.exists(self.print_file_name_path):
os.remove(self.print_file_name_path)
if os.path.exists(self.gcode.exclude_object_info):
os.remove(self.gcode.exclude_object_info)
call("sync", shell=True)
try:
power_loss_switch = False
if os.path.exists(self.user_print_refer_path):
with open(self.user_print_refer_path, "r") as f:
data = json.loads(f.read())
power_loss_switch = data.get("power_loss", {}).get("switch", False)
bl24c16f = self.printer.lookup_object('bl24c16f') if "bl24c16f" in self.printer.objects else None
if power_loss_switch and bl24c16f:
bl24c16f.setEepromDisable()
except Exception as err:
logging.error("power_loss gcode Z == 0: %s" % err)
error_message = "power_loss gcode Z == 0, stop print"
self.print_stats.note_error(error_message)
raise
gcode_move.cmd_CX_RESTORE_GCODE_STATE(print_info, self.print_file_name_path, XYZE)
logging.info("power_loss end do_resume success")
self.print_stats.power_loss = 0
if self.layer > 1:
self.slow_print = True
self.slow_count = self.layer + 1
self.speed_factor = gcode_move.speed_factor
self.gcode.run_script_from_command("M220 S20")
logging.info("power_loss slow_print M220 S20 SET")
except Exception as err:
self.print_stats.power_loss = 0
logging.error(err)
else:
self.gcode.run_script("G90")
except Exception as err:
self.print_stats.power_loss = 0
logging.exception("work_handler RESTORE_GCODE_STATE error: %s" % err)
if power_loss_switch and bl24c16f and self.current_file:
gcode_move.recordPrintFileName(self.print_file_name_path, self.current_file.name, slow_print=self.slow_print)
logging.info("Starting SD card print (position %d)", self.file_position)
self.reactor.unregister_timer(self.work_timer)
try:
self.current_file.seek(self.file_position)
except:
logging.exception("virtual_sdcard seek")
self.work_timer = None
return self.reactor.NEVER
self.print_stats.note_start()
gcode_mutex = self.gcode.get_mutex()
partial_input = ""
lines = []
error_message = None
lastE = 0
layer_count = 0
# self.gcode.run_script("G90")
toolhead = self.printer.lookup_object('toolhead')
start_time = interval_start_time = self.reactor.monotonic()
while not self.must_pause_work:
if not lines:
# Read more data
try:
data = self.current_file.read(8192)
except UnicodeDecodeError as err:
logging.exception(err)
err_msg = '{"code": "key571", "msg": "File UnicodeDecodeError"}'
self.gcode.respond_info(err_msg)
raise self.printer.command_error(err_msg)
except:
logging.exception("virtual_sdcard read")
break
if not data:
# End of file
self.current_file.close()
self.current_file = None
logging.info("Finished SD card print")
self.gcode.respond_raw("Done printing file")
if os.path.exists(self.print_file_name_path):
os.remove(self.print_file_name_path)
if os.path.exists(self.gcode.exclude_object_info):
os.remove(self.gcode.exclude_object_info)
if power_loss_switch and bl24c16f:
self.gcode.run_script("EEPROM_WRITE_BYTE ADDR=1 VAL=255")
self.first_layer_stop = False
self.print_first_layer = False
self.count_M204 = 0
self.layer = 0
self.layer_count = 0
self.update_print_history_info(only_update_status=True, state="completed")
time.sleep(0.3)
reportInformation("key701", data=self.cur_print_data)
self.cur_print_data = {}
self.print_id = ""
break
lines = data.split('\n')
lines[0] = partial_input + lines[0]
partial_input = lines.pop()
lines.reverse()
self.reactor.pause(self.reactor.NOW)
continue
# Pause if any other request is pending in the gcode class
if gcode_mutex.test():
self.reactor.pause(self.reactor.monotonic() + 0.100)
continue
# Dispatch command
self.cmd_from_sd = True
line = lines.pop()
next_file_position = self.file_position + len(line) + 1
self.next_file_position = next_file_position
end_time = interval_end_time = self.reactor.monotonic()
if self.count_line % 4999 == 0:
self.update_print_history_info()
try:
# if power_loss_switch and bl24c16f and (self.layer > 2 or (self.count_G1 > 18 and gcode_move.last_position[2] > 0.6)) and self.count_line % 99 == 0:
if power_loss_switch and bl24c16f and (self.layer > 2 or (self.count_G1 > 18 and gcode_move.last_position[2] > 0.6)) and end_time-start_time>5 and self.file_position>0:
start_time = end_time
base_position_e = round(list(gcode_move.base_position)[-1], 2)
pos = bl24c16f.eepromReadHeader()
if eepromState:
# eeprom first enable
self.gcode.run_script("EEPROM_WRITE_BYTE ADDR=1 VAL=1")
self.gcode.run_script("EEPROM_WRITE_INT ADDR=%s VAL=%s" % (pos*8, int(self.file_position)))
self.gcode.run_script("EEPROM_WRITE_FLOAT ADDR=%s VAL=%s" % (pos*8+4, base_position_e))
self.gcode.run_script("EEPROM_WRITE_BYTE ADDR=0 VAL=%d" % pos)
eepromState = False
else:
# pos = bl24c16f.eepromReadHeader()
if self.eepromWriteCount < 256:
self.gcode.run_script("EEPROM_WRITE_INT ADDR=%s VAL=%s" % (pos*8, int(self.file_position)))
self.gcode.run_script("EEPROM_WRITE_FLOAT ADDR=%s VAL=%s" % (pos*8+4, base_position_e))
else:
self.eepromWriteCount = 1
pos += 1
if pos == 256:
pos = 1
self.gcode.run_script("EEPROM_WRITE_INT ADDR=%s VAL=%s" % (pos*8, int(self.file_position)))
self.gcode.run_script("EEPROM_WRITE_FLOAT ADDR=%s VAL=%s" % (pos*8+4, base_position_e))
self.gcode.run_script("EEPROM_WRITE_BYTE ADDR=0 VAL=%d" % pos)
# logging.info("eepromWriteCount:%d, pos:%d" % (self.eepromWriteCount, pos))
self.eepromWriteCount += 1
if power_loss_switch and bl24c16f and self.count_G1 == 19:
gcode_move.recordPrintFileName(self.print_file_name_path, self.current_file.name, fan_state=self.fan_state, filament_used=self.print_stats.filament_used, last_print_duration=self.print_stats.print_duration, slow_print=self.slow_print)
# if power_loss_switch and bl24c16f and (self.layer > 2 or gcode_move.last_position[2] > 3) and self.count_line % 999 == 0:
if power_loss_switch and bl24c16f and (self.layer > 2 or gcode_move.last_position[2] > 3) and self.current_file and interval_end_time-interval_start_time > 30:
interval_start_time = interval_end_time
gcode_move.recordPrintFileName(self.print_file_name_path, self.current_file.name, fan_state=self.fan_state, filament_used=self.print_stats.filament_used, last_print_duration=self.print_stats.print_duration, slow_print=self.slow_print)
if line.startswith("G1") and "E" in line:
try:
E_str = line.split(" ")[-1]
if E_str.startswith("E"):
lastE = float(E_str.strip("\r").strip("\n")[1:])
except Exception as err:
pass
elif line.startswith("M106"):
self.fan_state = line.strip("\r").strip("\n")
if power_loss_switch and bl24c16f:
gcode_move.recordPrintFileName(self.print_file_name_path, self.current_file.name, fan_state=self.fan_state, slow_print=self.slow_print)
self.fan_state = ""
elif line.startswith("END_PRINT"):
if os.path.exists(self.print_file_name_path):
os.remove(self.print_file_name_path)
if os.path.exists(self.gcode.exclude_object_info):
os.remove(self.gcode.exclude_object_info)
if power_loss_switch and bl24c16f:
self.gcode.run_script("EEPROM_WRITE_BYTE ADDR=1 VAL=255")
elif line.startswith("M900"):
gcode_move.recordPrintFileName(self.print_file_name_path, self.current_file.name, fan_state=self.fan_state, pressure_advance=line.strip("\r").strip("\n"), slow_print=self.slow_print)
for layer_key in LAYER_KEYS:
if line.startswith(layer_key):
self.layer += 1
self.record_layer(self.layer)
if self.print_first_layer and self.count_G1 >= 20:
for layer_key in LAYER_KEYS:
if line.startswith(layer_key):
logging.info("print_first_layer layer_key:%s" % layer_key)
X, Y, Z, E = toolhead.get_position()
self.gcode.run_script("FIRST_FLOOR_PAUSE")
self.first_layer_stop = True
if delay_photography_switch:
for layer_key in LAYER_KEYS:
if ";LAYER_COUNT:" in layer_key:
break
if line.startswith(layer_key) and os.path.exists("/tmp/camera_main"):
if layer_count % int(interval) == 0:
if location:
cmd_wait_for_stepper = "M400"
# toolhead = self.printer.lookup_object('toolhead')
# self.gcode.run_script(cmd_wait_for_stepper)
X, Y, Z, E = toolhead.get_position()
if self.count_G1 >= 20:
self.toolhead_moved = True
# 1. Pull back and lift first
logging.info("G1 F2400 E%s" % (lastE-3))
logging.info(cmd_wait_for_stepper)
self.gcode.run_script_from_command("G1 F2400 E%s" % (lastE-3))
self.gcode.run_script_from_command(cmd_wait_for_stepper)
time.sleep(0.1)
logging.info("G1 F3000 Z%s layer:%s" % ((Z + 2), self.layer))
self.gcode.run_script_from_command("G1 F3000 Z%s" % (Z + 2))
self.gcode.run_script_from_command(cmd_wait_for_stepper)
self.timelapse_move(self.print_file_name_path, z_upraise=2)
time.sleep(0.1)
# 2. move to the specified position
cmd = "G0 X5 Y150 F15000"
logging.info(cmd)
logging.info(cmd_wait_for_stepper)
self.gcode.run_script_from_command(cmd)
self.gcode.run_script_from_command(cmd_wait_for_stepper)
try:
capture_shell = "capture 0"
logging.info(capture_shell)
capture_ret = check_output(capture_shell, shell=True).decode("utf-8")
logging.info("capture 0 return:#%s#" % str(capture_ret))
except Exception as err:
logging.error(err)
time.sleep(0.1)
# 3. move back
move_back_cmd = "G0 X%s Y%s F15000" % (X, Y)
logging.info(move_back_cmd)
logging.info(cmd_wait_for_stepper)
self.gcode.run_script_from_command(move_back_cmd)
self.gcode.run_script_from_command(cmd_wait_for_stepper)
time.sleep(0.2)
logging.info("G1 F3000 Z%s layer:%s" % (Z, self.layer))
self.gcode.run_script_from_command("G1 F3000 Z%s" % Z)
self.gcode.run_script_from_command(cmd_wait_for_stepper)
time.sleep(0.1)
logging.info("G1 F2400 E%s" % (lastE))
self.gcode.run_script_from_command("G1 F2400 E%s" % (lastE))
self.gcode.run_script_from_command(cmd_wait_for_stepper)
time.sleep(0.2)
else:
try:
capture_shell = "capture 0"
logging.info(capture_shell)
capture_ret = check_output(capture_shell, shell=True).decode("utf-8")
logging.info("capture 0 return:#%s#" % str(capture_ret))
except Exception as err:
logging.error(err)
layer_count += 1
break
if self.slow_print == True and self.layer > 0 and self.slow_count < self.layer:
self.resume_print_speed()
self.toolhead_moved = False
self.gcode.run_script(line)
self.count_line += 1
if self.count_G1 < 20 and line.startswith("G1"):
self.count_G1 += 1
except self.gcode.error as e:
error_message = str(e)
try:
self.gcode.run_script(self.on_error_gcode.render())
except:
logging.exception("virtual_sdcard on_error")
self.layer = 0
self.layer_count = 0
self.resume_print_speed()
break
except:
logging.exception("virtual_sdcard dispatch")
self.layer = 0
self.layer_count = 0
self.resume_print_speed()
break
self.cmd_from_sd = False
self.file_position = self.next_file_position
# Do we need to skip around?
if self.next_file_position != next_file_position:
try:
self.current_file.seek(self.file_position)
except:
logging.exception("virtual_sdcard seek")
self.work_timer = None
return self.reactor.NEVER
lines = []
partial_input = ""
logging.info("Exiting SD card print (position %d)", self.file_position)
self.toolhead_moved = False
self.count_line = 0
self.count_G1 = 0
self.do_resume_status = False
self.eepromWriteCount = 1
self.work_timer = None
self.cmd_from_sd = False
if error_message is not None:
self.print_stats.note_error(error_message)
elif self.current_file is not None:
self.print_stats.note_pause()
else:
self.print_stats.note_complete()
return self.reactor.NEVER
def timelapse_move(self, print_file_name_save_path, z_upraise):
if not os.path.exists(print_file_name_save_path):
return
try:
result = {}
with open(print_file_name_save_path, "r") as f:
result = json.loads(f.read())
with open(print_file_name_save_path, "w") as f:
result["z_toolhead_moved"] = z_upraise
f.write(json.dumps(result))
f.flush()
except Exception as err:
logging.error(err)
def load_config(config):
return VirtualSD(config)