mirror of
https://github.com/Dark98/Ender-3_V3_KE_Klipper.git
synced 2026-07-06 00:38:38 +00:00
880 lines
44 KiB
Python
Executable File
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)
|