mirror of https://github.com/Desuuuu/klipper.git
klippy: Rework shutdown handling
If an MCU signals a shutdown from the background thread, notify the main thread and handle the shutdown there. Dispatch shutdown handling from the main Printer() class instead of from the Toolhead class. This simplifies the shutdown logic. Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
This commit is contained in:
parent
3033b03789
commit
f8750b142f
|
@ -23,8 +23,9 @@ class GCodeParser:
|
||||||
self.bytes_read = 0
|
self.bytes_read = 0
|
||||||
self.input_log = collections.deque([], 50)
|
self.input_log = collections.deque([], 50)
|
||||||
# Command handling
|
# Command handling
|
||||||
self.gcode_handlers = self.build_handlers(False)
|
|
||||||
self.is_printer_ready = False
|
self.is_printer_ready = False
|
||||||
|
self.gcode_handlers = {}
|
||||||
|
self.build_handlers()
|
||||||
self.need_ack = False
|
self.need_ack = False
|
||||||
self.toolhead = self.fan = self.extruder = None
|
self.toolhead = self.fan = self.extruder = None
|
||||||
self.heaters = []
|
self.heaters = []
|
||||||
|
@ -34,28 +35,21 @@ class GCodeParser:
|
||||||
self.last_position = [0.0, 0.0, 0.0, 0.0]
|
self.last_position = [0.0, 0.0, 0.0, 0.0]
|
||||||
self.homing_add = [0.0, 0.0, 0.0, 0.0]
|
self.homing_add = [0.0, 0.0, 0.0, 0.0]
|
||||||
self.axis2pos = {'X': 0, 'Y': 1, 'Z': 2, 'E': 3}
|
self.axis2pos = {'X': 0, 'Y': 1, 'Z': 2, 'E': 3}
|
||||||
def build_handlers(self, is_ready):
|
def build_handlers(self):
|
||||||
handlers = self.all_handlers
|
handlers = self.all_handlers
|
||||||
if not is_ready:
|
if not self.is_printer_ready:
|
||||||
handlers = [h for h in handlers
|
handlers = [h for h in handlers
|
||||||
if getattr(self, 'cmd_'+h+'_when_not_ready', False)]
|
if getattr(self, 'cmd_'+h+'_when_not_ready', False)]
|
||||||
gcode_handlers = { h: getattr(self, 'cmd_'+h) for h in handlers }
|
gcode_handlers = { h: getattr(self, 'cmd_'+h) for h in handlers }
|
||||||
for h, f in list(gcode_handlers.items()):
|
for h, f in list(gcode_handlers.items()):
|
||||||
aliases = getattr(self, 'cmd_'+h+'_aliases', [])
|
aliases = getattr(self, 'cmd_'+h+'_aliases', [])
|
||||||
gcode_handlers.update({ a: f for a in aliases })
|
gcode_handlers.update({ a: f for a in aliases })
|
||||||
return gcode_handlers
|
self.gcode_handlers = gcode_handlers
|
||||||
def stats(self, eventtime):
|
def stats(self, eventtime):
|
||||||
return "gcodein=%d" % (self.bytes_read,)
|
return "gcodein=%d" % (self.bytes_read,)
|
||||||
def set_printer_ready(self, is_ready):
|
def connect(self):
|
||||||
if self.is_printer_ready == is_ready:
|
self.is_printer_ready = True
|
||||||
return
|
self.build_handlers()
|
||||||
self.is_printer_ready = is_ready
|
|
||||||
self.gcode_handlers = self.build_handlers(is_ready)
|
|
||||||
if not is_ready:
|
|
||||||
# Printer is shutdown (could be running in a background thread)
|
|
||||||
if self.is_fileinput:
|
|
||||||
self.printer.request_exit()
|
|
||||||
return
|
|
||||||
# Lookup printer components
|
# Lookup printer components
|
||||||
self.toolhead = self.printer.objects.get('toolhead')
|
self.toolhead = self.printer.objects.get('toolhead')
|
||||||
extruders = extruder.get_printer_extruders(self.printer)
|
extruders = extruder.get_printer_extruders(self.printer)
|
||||||
|
@ -67,6 +61,14 @@ class GCodeParser:
|
||||||
self.fan = self.printer.objects.get('fan')
|
self.fan = self.printer.objects.get('fan')
|
||||||
if self.is_fileinput and self.fd_handle is None:
|
if self.is_fileinput and self.fd_handle is None:
|
||||||
self.fd_handle = self.reactor.register_fd(self.fd, self.process_data)
|
self.fd_handle = self.reactor.register_fd(self.fd, self.process_data)
|
||||||
|
def do_shutdown(self):
|
||||||
|
if not self.is_printer_ready:
|
||||||
|
return
|
||||||
|
self.is_printer_ready = False
|
||||||
|
self.build_handlers()
|
||||||
|
self.dump_debug()
|
||||||
|
if self.is_fileinput:
|
||||||
|
self.printer.request_exit()
|
||||||
def motor_heater_off(self):
|
def motor_heater_off(self):
|
||||||
if self.toolhead is None:
|
if self.toolhead is None:
|
||||||
return
|
return
|
||||||
|
@ -114,12 +116,10 @@ class GCodeParser:
|
||||||
except error as e:
|
except error as e:
|
||||||
self.respond_error(str(e))
|
self.respond_error(str(e))
|
||||||
except:
|
except:
|
||||||
logging.exception("Exception in command handler")
|
msg = 'Internal error on command:"%s"' % (cmd,)
|
||||||
self.toolhead.force_shutdown()
|
logging.exception(msg)
|
||||||
self.respond_error('Internal error on command:"%s"' % (cmd,))
|
self.printer.invoke_shutdown(msg)
|
||||||
if self.is_fileinput:
|
self.respond_error(msg)
|
||||||
self.printer.request_exit()
|
|
||||||
break
|
|
||||||
self.ack()
|
self.ack()
|
||||||
self.need_ack = prev_need_ack
|
self.need_ack = prev_need_ack
|
||||||
def process_data(self, eventtime):
|
def process_data(self, eventtime):
|
||||||
|
@ -396,7 +396,7 @@ class GCodeParser:
|
||||||
self.set_temp(params, wait=True)
|
self.set_temp(params, wait=True)
|
||||||
def cmd_M112(self, params):
|
def cmd_M112(self, params):
|
||||||
# Emergency Stop
|
# Emergency Stop
|
||||||
self.toolhead.force_shutdown()
|
self.printer.invoke_shutdown("Shutdown due to M112 command")
|
||||||
cmd_M114_when_not_ready = True
|
cmd_M114_when_not_ready = True
|
||||||
def cmd_M114(self, params):
|
def cmd_M114(self, params):
|
||||||
# Get Current Position
|
# Get Current Position
|
||||||
|
|
|
@ -136,18 +136,15 @@ class Printer:
|
||||||
self.connect_timer = self.reactor.register_timer(
|
self.connect_timer = self.reactor.register_timer(
|
||||||
self._connect, self.reactor.NOW)
|
self._connect, self.reactor.NOW)
|
||||||
self.all_config_options = {}
|
self.all_config_options = {}
|
||||||
self.need_dump_debug = False
|
|
||||||
self.state_message = message_startup
|
self.state_message = message_startup
|
||||||
|
self.is_shutdown = False
|
||||||
|
self.async_shutdown_msg = ""
|
||||||
self.run_result = None
|
self.run_result = None
|
||||||
self.fileconfig = None
|
self.fileconfig = None
|
||||||
self.mcus = []
|
self.mcus = []
|
||||||
def get_start_args(self):
|
def get_start_args(self):
|
||||||
return self.start_args
|
return self.start_args
|
||||||
def _stats(self, eventtime, force_output=False):
|
def _stats(self, eventtime, force_output=False):
|
||||||
if self.need_dump_debug:
|
|
||||||
# Call dump_debug here so it is executed in the main thread
|
|
||||||
self.gcode.dump_debug()
|
|
||||||
self.need_dump_debug = False
|
|
||||||
toolhead = self.objects.get('toolhead')
|
toolhead = self.objects.get('toolhead')
|
||||||
if toolhead is None:
|
if toolhead is None:
|
||||||
return eventtime + 1.
|
return eventtime + 1.
|
||||||
|
@ -196,7 +193,7 @@ class Printer:
|
||||||
self._load_config()
|
self._load_config()
|
||||||
for m in self.mcus:
|
for m in self.mcus:
|
||||||
m.connect()
|
m.connect()
|
||||||
self.gcode.set_printer_ready(True)
|
self.gcode.connect()
|
||||||
self.state_message = message_ready
|
self.state_message = message_ready
|
||||||
if self.start_args.get('debugoutput') is None:
|
if self.start_args.get('debugoutput') is None:
|
||||||
self.reactor.update_timer(self.stats_timer, self.reactor.NOW)
|
self.reactor.update_timer(self.stats_timer, self.reactor.NOW)
|
||||||
|
@ -219,34 +216,46 @@ class Printer:
|
||||||
monotime = self.reactor.monotonic()
|
monotime = self.reactor.monotonic()
|
||||||
logging.info("Start printer at %s (%.1f %.1f)",
|
logging.info("Start printer at %s (%.1f %.1f)",
|
||||||
time.asctime(time.localtime(systime)), systime, monotime)
|
time.asctime(time.localtime(systime)), systime, monotime)
|
||||||
# Enter main reactor loop
|
while 1:
|
||||||
try:
|
# Enter main reactor loop
|
||||||
self.reactor.run()
|
try:
|
||||||
except:
|
self.reactor.run()
|
||||||
logging.exception("Unhandled exception during run")
|
except:
|
||||||
return "exit"
|
logging.exception("Unhandled exception during run")
|
||||||
# Check restart flags
|
return "exit"
|
||||||
run_result = self.run_result
|
# Check restart flags
|
||||||
try:
|
run_result = self.run_result
|
||||||
self._stats(self.reactor.monotonic(), force_output=True)
|
try:
|
||||||
for m in self.mcus:
|
if run_result == 'shutdown':
|
||||||
if run_result == 'firmware_restart':
|
self.invoke_shutdown(self.async_shutdown_msg, True)
|
||||||
m.microcontroller_restart()
|
continue
|
||||||
m.disconnect()
|
self._stats(self.reactor.monotonic(), force_output=True)
|
||||||
except:
|
for m in self.mcus:
|
||||||
logging.exception("Unhandled exception during post run")
|
if run_result == 'firmware_restart':
|
||||||
return run_result
|
m.microcontroller_restart()
|
||||||
|
m.disconnect()
|
||||||
|
except:
|
||||||
|
logging.exception("Unhandled exception during post run")
|
||||||
|
return run_result
|
||||||
def get_state_message(self):
|
def get_state_message(self):
|
||||||
return self.state_message
|
return self.state_message
|
||||||
def note_shutdown(self, msg):
|
def invoke_shutdown(self, msg, is_mcu_shutdown=False):
|
||||||
if self.state_message == message_ready:
|
if self.is_shutdown:
|
||||||
self.need_dump_debug = True
|
return
|
||||||
self.state_message = "%s%s" % (msg, message_shutdown)
|
self.is_shutdown = True
|
||||||
self.gcode.set_printer_ready(False)
|
if is_mcu_shutdown:
|
||||||
def note_mcu_error(self, msg):
|
self.state_message = "%s%s" % (msg, message_shutdown)
|
||||||
self.state_message = "%s%s" % (msg, message_restart)
|
else:
|
||||||
self.gcode.set_printer_ready(False)
|
self.state_message = "%s%s" % (msg, message_restart)
|
||||||
self.gcode.motor_heater_off()
|
for m in self.mcus:
|
||||||
|
m.do_shutdown()
|
||||||
|
self.gcode.do_shutdown()
|
||||||
|
toolhead = self.objects.get('toolhead')
|
||||||
|
if toolhead is not None:
|
||||||
|
toolhead.do_shutdown()
|
||||||
|
def invoke_async_shutdown(self, msg):
|
||||||
|
self.async_shutdown_msg = msg
|
||||||
|
self.request_exit("shutdown")
|
||||||
def request_exit(self, result="exit"):
|
def request_exit(self, result="exit"):
|
||||||
self.run_result = result
|
self.run_result = result
|
||||||
self.reactor.end()
|
self.reactor.end()
|
||||||
|
|
|
@ -472,7 +472,7 @@ class MCU:
|
||||||
prefix = "MCU '%s' shutdown: " % (self._name,)
|
prefix = "MCU '%s' shutdown: " % (self._name,)
|
||||||
if params['#name'] == 'is_shutdown':
|
if params['#name'] == 'is_shutdown':
|
||||||
prefix = "Previous MCU '%s' shutdown: " % (self._name,)
|
prefix = "Previous MCU '%s' shutdown: " % (self._name,)
|
||||||
self._printer.note_shutdown(prefix + msg + error_help(msg))
|
self._printer.invoke_async_shutdown(prefix + msg + error_help(msg))
|
||||||
# Connection phase
|
# Connection phase
|
||||||
def _check_restart(self, reason):
|
def _check_restart(self, reason):
|
||||||
start_reason = self._printer.get_start_args().get("start_reason")
|
start_reason = self._printer.get_start_args().get("start_reason")
|
||||||
|
@ -723,11 +723,11 @@ class MCU:
|
||||||
return
|
return
|
||||||
offset, freq = self._clocksync.calibrate_clock(print_time, eventtime)
|
offset, freq = self._clocksync.calibrate_clock(print_time, eventtime)
|
||||||
self._ffi_lib.steppersync_set_time(self._steppersync, offset, freq)
|
self._ffi_lib.steppersync_set_time(self._steppersync, offset, freq)
|
||||||
if self._clocksync.is_active(eventtime):
|
if self._clocksync.is_active(eventtime) or self.is_fileoutput():
|
||||||
return
|
return
|
||||||
logging.info("Timeout with MCU '%s' (eventtime=%f)",
|
logging.info("Timeout with MCU '%s' (eventtime=%f)",
|
||||||
self._name, eventtime)
|
self._name, eventtime)
|
||||||
self._printer.note_mcu_error("Lost communication with MCU '%s'" % (
|
self._printer.invoke_shutdown("Lost communication with MCU '%s'" % (
|
||||||
self._name,))
|
self._name,))
|
||||||
def stats(self, eventtime):
|
def stats(self, eventtime):
|
||||||
msg = "%s: mcu_awake=%.03f mcu_task_avg=%.06f mcu_task_stddev=%.06f" % (
|
msg = "%s: mcu_awake=%.03f mcu_task_avg=%.06f mcu_task_stddev=%.06f" % (
|
||||||
|
@ -735,7 +735,9 @@ class MCU:
|
||||||
self._mcu_tick_stddev)
|
self._mcu_tick_stddev)
|
||||||
return ' '.join([msg, self._serial.stats(eventtime),
|
return ' '.join([msg, self._serial.stats(eventtime),
|
||||||
self._clocksync.stats(eventtime)])
|
self._clocksync.stats(eventtime)])
|
||||||
def force_shutdown(self):
|
def do_shutdown(self):
|
||||||
|
if self._is_shutdown or self._emergency_stop_cmd is None:
|
||||||
|
return
|
||||||
self.send(self._emergency_stop_cmd.encode())
|
self.send(self._emergency_stop_cmd.encode())
|
||||||
def disconnect(self):
|
def disconnect(self):
|
||||||
self._serial.disconnect()
|
self._serial.disconnect()
|
||||||
|
|
|
@ -292,7 +292,7 @@ class ToolHead:
|
||||||
self.last_flush_from_idle = True
|
self.last_flush_from_idle = True
|
||||||
except:
|
except:
|
||||||
logging.exception("Exception in flush_handler")
|
logging.exception("Exception in flush_handler")
|
||||||
self.force_shutdown()
|
self.printer.invoke_shutdown("Exception in flush_handler")
|
||||||
return self.reactor.NEVER
|
return self.reactor.NEVER
|
||||||
# Motor off timer
|
# Motor off timer
|
||||||
def _motor_off_handler(self, eventtime):
|
def _motor_off_handler(self, eventtime):
|
||||||
|
@ -305,7 +305,7 @@ class ToolHead:
|
||||||
self.motor_off()
|
self.motor_off()
|
||||||
except:
|
except:
|
||||||
logging.exception("Exception in motor_off_handler")
|
logging.exception("Exception in motor_off_handler")
|
||||||
self.force_shutdown()
|
self.printer.invoke_shutdown("Exception in motor_off_handler")
|
||||||
return eventtime + self.motor_off_time
|
return eventtime + self.motor_off_time
|
||||||
# Movement commands
|
# Movement commands
|
||||||
def get_position(self):
|
def get_position(self):
|
||||||
|
@ -371,14 +371,12 @@ class ToolHead:
|
||||||
buffer_time = max(0., self.print_time - est_print_time)
|
buffer_time = max(0., self.print_time - est_print_time)
|
||||||
return "print_time=%.3f buffer_time=%.3f print_stall=%d" % (
|
return "print_time=%.3f buffer_time=%.3f print_stall=%d" % (
|
||||||
self.print_time, buffer_time, self.print_stall)
|
self.print_time, buffer_time, self.print_stall)
|
||||||
def force_shutdown(self):
|
def do_shutdown(self):
|
||||||
try:
|
try:
|
||||||
for m in self.all_mcus:
|
|
||||||
m.force_shutdown()
|
|
||||||
self.move_queue.reset()
|
self.move_queue.reset()
|
||||||
self.reset_print_time()
|
self.reset_print_time()
|
||||||
except:
|
except:
|
||||||
logging.exception("Exception in force_shutdown")
|
logging.exception("Exception in do_shutdown")
|
||||||
def get_max_velocity(self):
|
def get_max_velocity(self):
|
||||||
return self.max_velocity, self.max_accel
|
return self.max_velocity, self.max_accel
|
||||||
def get_max_axis_halt(self):
|
def get_max_axis_halt(self):
|
||||||
|
|
Loading…
Reference in New Issue