Skip to content

Commit

Permalink
Add Qidi Plus 4
Browse files Browse the repository at this point in the history
  • Loading branch information
liberodark committed Jan 30, 2025
1 parent 0114d72 commit 16f2597
Show file tree
Hide file tree
Showing 15 changed files with 553 additions and 6 deletions.
59 changes: 58 additions & 1 deletion klippy/configfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,8 @@ def __init__(self, printer):
gcode = self.printer.lookup_object('gcode')
gcode.register_command("SAVE_CONFIG", self.cmd_SAVE_CONFIG,
desc=self.cmd_SAVE_CONFIG_help)
gcode.register_command("SAVE_CONFIG_QD", self.cmd_SAVE_CONFIG_QD,
desc=self.cmd_SAVE_CONFIG_QD_help)
def _find_autosave_data(self, data):
regular_data = data
autosave_data = ""
Expand Down Expand Up @@ -397,16 +399,66 @@ def cmd_SAVE_CONFIG(self, gcmd):
try:
f = open(temp_name, 'w')
f.write(data)
f.flush()
f.close()
os.rename(cfgname, backup_name)
os.rename(temp_name, cfgname)
os.system("sync")
except:
msg = "Unable to write config file during SAVE_CONFIG"
logging.exception(msg)
raise gcmd.error(msg)
# Request a restart
gcode = self.printer.lookup_object('gcode')
gcode.request_restart('restart')
gcode.request_restart('firmware_restart')

cmd_SAVE_CONFIG_QD_help = "Overwrite config file and restart"
def cmd_SAVE_CONFIG_QD(self, gcmd):
if not self.autosave.fileconfig.sections():
return
gcode = self.printer.lookup_object('gcode')
# Create string containing autosave data
autosave_data = self._build_config_string(self.autosave)
lines = [('#*# ' + l).strip()
for l in autosave_data.split('\n')]
lines.insert(0, "\n" + AUTOSAVE_HEADER.rstrip())
lines.append("")
autosave_data = '\n'.join(lines)
# Read in and validate current config file
cfgname = self.printer.get_start_args()['config_file']
try:
data = self._read_config_file(cfgname)
regular_data, old_autosave_data = self._find_autosave_data(data)
config = self._build_config_wrapper(regular_data, cfgname)
except error as e:
msg = "Unable to parse existing config on SAVE_CONFIG"
logging.exception(msg)
raise gcode.error(msg)
regular_data = self._strip_duplicates(regular_data, self.autosave)
self._disallow_include_conflicts(regular_data, cfgname, gcode)
data = regular_data.rstrip() + autosave_data
# Determine filenames
datestr = time.strftime("-%Y%m%d_%H%M%S")
backup_name = cfgname + datestr
temp_name = cfgname + "_autosave"
if cfgname.endswith(".cfg"):
backup_name = cfgname[:-4] + datestr + ".cfg"
temp_name = cfgname[:-4] + "_autosave.cfg"
# Create new config file with temporary name and swap with main config
logging.info("SAVE_CONFIG to '%s' (backup in '%s')",
cfgname, backup_name)
try:
f = open(temp_name, 'w')
f.write(data)
f.flush()
f.close()
os.rename(cfgname, backup_name)
os.rename(temp_name, cfgname)
os.system("sync")
except:
msg = "Unable to write config file during SAVE_CONFIG"
logging.exception(msg)
raise gcode.error(msg)


######################################################################
Expand Down Expand Up @@ -518,6 +570,11 @@ def deprecate(self, section, option, value=None, msg=None):
self.deprecate_warnings.append(res)
self.status_warnings = self.runtime_warnings + self.deprecate_warnings
# Status reporting
def runtime_warning(self, msg):
logging.warning(msg)
res = {'type': 'runtime_warning', 'message': msg}
self.runtime_warnings.append(res)
self.status_warnings = self.runtime_warnings + self.deprecate_warnings
def _build_status_config(self, config):
self.status_raw_config = {}
for section in config.get_prefix_sections(''):
Expand Down
52 changes: 52 additions & 0 deletions klippy/extras/chamber_fan.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from . import fan

PIN_MIN_TIME = 0.100

class ChamberFan:
def __init__(self, config):
self.printer = config.get_printer()
self.printer.register_event_handler("klippy:ready", self.handle_ready)
self.printer.register_event_handler("klippy:connect",
self.handle_connect)
self.printer.load_object(config, 'heaters')
self.heaters = []
self.fan = fan.Fan(config)
self.fan_speed = config.getfloat('fan_speed', default=1.,
minval=0., maxval=1.)
self.idle_speed = config.getfloat(
'idle_speed', default=self.fan_speed, minval=0., maxval=1.)
self.idle_timeout = config.getint("idle_timeout", default=30, minval=0)
self.heater_names = config.getlist("heater", ())
self.last_on = self.idle_timeout
self.last_speed = 0.
def handle_connect(self):
# Heater lookup
pheaters = self.printer.lookup_object('heaters')
self.heaters = [pheaters.lookup_heater(n) for n in self.heater_names]
def handle_ready(self):
reactor = self.printer.get_reactor()
reactor.register_timer(self.callback, reactor.monotonic()+PIN_MIN_TIME)
def get_status(self, eventtime):
return self.fan.get_status(eventtime)
def callback(self, eventtime):
speed = 0.
active = False
for heater in self.heaters:
_, target_temp = heater.get_temp(eventtime)
if target_temp:
active = True
if active:
self.last_on = 0
speed = self.fan_speed
elif self.last_on < self.idle_timeout:
speed = self.idle_speed
self.last_on += 1
if speed != self.last_speed:
self.last_speed = speed
curtime = self.printer.get_reactor().monotonic()
print_time = self.fan.get_mcu().estimated_print_time(curtime)
self.fan.set_speed(print_time + PIN_MIN_TIME, speed)
return eventtime + 1.

def load_config_prefix(config):
return ChamberFan(config)
15 changes: 15 additions & 0 deletions klippy/extras/gcode_macro_break.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class GCodeMacroBreaker:
def __init__(self, config):
# Gcode macro interupt
self.printer = config.get_printer()
webhooks = self.printer.lookup_object('webhooks')
webhooks.register_endpoint("breakmacro", self._handle_breakmacro)
webhooks.register_endpoint("resumemacro", self._handle_resumemacro)
self.gcode = self.printer.lookup_object('gcode')
def _handle_breakmacro(self, web_request):
self.gcode.break_flag = True
def _handle_resumemacro(self, web_request):
self.gcode.break_flag = False

def load_config(config):
return GCodeMacroBreaker(config)
24 changes: 24 additions & 0 deletions klippy/extras/gcode_move.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ def __init__(self, config):
self.saved_states = {}
self.move_transform = self.move_with_transform = None
self.position_with_transform = (lambda: [0., 0., 0., 0.])
# Save and load z offset
gcode.register_command(
'SAVE_ZOFFSET_TO_VARIABLE',
self.cmd_SAVE_ZOFFSET_TO_VARIABLE
)
gcode.register_command(
'LOAD_ZOFFSET_FROM_VARIABLE',
self.cmd_LOAD_ZOFFSET_FROM_VARIABLE
)
def _handle_ready(self):
self.is_printer_ready = True
if self.move_transform is None:
Expand Down Expand Up @@ -272,5 +281,20 @@ def cmd_GET_POSITION(self, gcmd):
% (mcu_pos, stepper_pos, kin_pos, toolhead_pos,
gcode_pos, base_pos, homing_pos))

def cmd_SAVE_ZOFFSET_TO_VARIABLE(self, gcmd):
variables = self.printer.lookup_object("save_variables")
gcode_move = self.printer.lookup_object("gcode_move")
variables.save_variable(
'Variables', 'z_offset',
gcode_move.homing_position[2]
)

def cmd_LOAD_ZOFFSET_FROM_VARIABLE(self, gcmd):
variables = self.printer.lookup_object("save_variables")
gcode_move = self.printer.lookup_object("gcode_move")
gcode_move.homing_position[2] = float(
variables.load_variable('Variables', 'z_offset')
)

def load_config(config):
return GCodeMove(config)
87 changes: 87 additions & 0 deletions klippy/extras/gcode_shell_command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Run a shell command via gcode
#
# Copyright (C) 2019 Eric Callahan <arksine.code@gmail.com>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import os
import shlex
import subprocess
import logging

class ShellCommand:
def __init__(self, config):
self.name = config.get_name().split()[-1]
self.printer = config.get_printer()
self.gcode = self.printer.lookup_object('gcode')
cmd = config.get('command')
cmd = os.path.expanduser(cmd)
self.command = shlex.split(cmd)
self.timeout = config.getfloat('timeout', 2., above=0.)
self.verbose = config.getboolean('verbose', True)
self.proc_fd = None
self.partial_output = ""
self.gcode.register_mux_command(
"RUN_SHELL_COMMAND", "CMD", self.name,
self.cmd_RUN_SHELL_COMMAND,
desc=self.cmd_RUN_SHELL_COMMAND_help)

def _process_output(self, eventime):
if self.proc_fd is None:
return
try:
data = os.read(self.proc_fd, 4096)
except Exception:
pass
data = self.partial_output + data.decode()
if '\n' not in data:
self.partial_output = data
return
elif data[-1] != '\n':
split = data.rfind('\n') + 1
self.partial_output = data[split:]
data = data[:split]
else:
self.partial_output = ""
self.gcode.respond_info(data)

cmd_RUN_SHELL_COMMAND_help = "Run a linux shell command"
def cmd_RUN_SHELL_COMMAND(self, params):
gcode_params = params.get('PARAMS','')
gcode_params = shlex.split(gcode_params)
reactor = self.printer.get_reactor()
try:
proc = subprocess.Popen(
self.command + gcode_params, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
except Exception:
logging.exception(
"shell_command: Command {%s} failed" % (self.name))
raise self.gcode.error("Error running command {%s}" % (self.name))
if self.verbose:
self.proc_fd = proc.stdout.fileno()
self.gcode.respond_info("Running Command {%s}...:" % (self.name))
hdl = reactor.register_fd(self.proc_fd, self._process_output)
eventtime = reactor.monotonic()
endtime = eventtime + self.timeout
complete = False
while eventtime < endtime:
eventtime = reactor.pause(eventtime + .05)
if proc.poll() is not None:
complete = True
break
if not complete:
proc.terminate()
if self.verbose:
if self.partial_output:
self.gcode.respond_info(self.partial_output)
self.partial_output = ""
if complete:
msg = "Command {%s} finished\n" % (self.name)
else:
msg = "Command {%s} timed out" % (self.name)
self.gcode.respond_info(msg)
reactor.unregister_fd(hdl)
self.proc_fd = None


def load_config_prefix(config):
return ShellCommand(config)
16 changes: 15 additions & 1 deletion klippy/extras/heaters.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,16 @@ def __init__(self, config):
gcode.register_command("M105", self.cmd_M105, when_not_ready=True)
gcode.register_command("TEMPERATURE_WAIT", self.cmd_TEMPERATURE_WAIT,
desc=self.cmd_TEMPERATURE_WAIT_help)
# Wait heater interupt
webhooks = self.printer.lookup_object('webhooks')
webhooks.register_endpoint("breakheater", self._handle_breakheater)
self.break_flag=False
def _handle_breakheater(self,web_request):
reactor = self.printer.get_reactor()
for heater in self.heaters.values():
eventtime = reactor.monotonic()
if heater.check_busy(eventtime):
self.break_flag = True
def load_config(self, config):
self.have_load_sensors = True
# Load default temperature sensors
Expand Down Expand Up @@ -345,7 +355,11 @@ def _wait_for_temperature(self, heater):
gcode = self.printer.lookup_object("gcode")
reactor = self.printer.get_reactor()
eventtime = reactor.monotonic()
self.break_flag = False
while not self.printer.is_shutdown() and heater.check_busy(eventtime):
if self.break_flag:
self.break_flag = False
break
print_time = toolhead.get_last_move_time()
gcode.respond_raw(self._get_temp(eventtime))
eventtime = reactor.pause(eventtime + 1.)
Expand Down Expand Up @@ -374,7 +388,7 @@ def cmd_TEMPERATURE_WAIT(self, gcmd):
toolhead = self.printer.lookup_object("toolhead")
reactor = self.printer.get_reactor()
eventtime = reactor.monotonic()
while not self.printer.is_shutdown():
while not self.printer.is_shutdown() and not self.break_flag:
temp, target = sensor.get_temp(eventtime)
if temp >= min_temp and temp <= max_temp:
return
Expand Down
4 changes: 2 additions & 2 deletions klippy/extras/homing.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,8 +264,8 @@ def probing_move(self, mcu_probe, pos, speed):
"Probing failed due to printer shutdown")
raise
if hmove.check_no_movement() is not None:
raise self.printer.command_error(
"Probe triggered prior to movement")
gcode = self.printer.lookup_object('gcode')
gcode.respond_info('Probe triggered prior to movement')
return epos
def cmd_G28(self, gcmd):
# Move to origin
Expand Down
Loading

0 comments on commit 16f2597

Please sign in to comment.