diff --git a/config/example-extras.cfg b/config/example-extras.cfg index 84d8a05a..e6c0adae 100644 --- a/config/example-extras.cfg +++ b/config/example-extras.cfg @@ -165,6 +165,17 @@ # default is 120. +# Idle timeout. An idle timeout is automatically enabled - add an +# explicit idle_timeout config section to change the default settings. +#[idle_timeout] +#gcode: +# A list of G-Code commands (one per line) to execute on an idle +# timeout. The default is to run "M84". +#timeout: 600 +# Idle time (in seconds) to wait before running the above G-Code +# commands. The default is 600 seconds. + + # Multi-stepper axes. On a cartesian style printer, the stepper # controlling a given axis may have additional config blocks defining # steppers that should be stepped in concert with the primary diff --git a/config/example.cfg b/config/example.cfg index d8ca95fa..025f6b71 100644 --- a/config/example.cfg +++ b/config/example.cfg @@ -293,9 +293,6 @@ max_z_accel: 30 # mm/s^2) of movement along the z axis. It limits the acceleration # of the z stepper motor on cartesian printers. The default is to # use max_accel for max_z_accel. -#motor_off_time: 600 -# Time (in seconds) of idle time before the printer will try to -# disable active motors. The default is 600 seconds. #square_corner_velocity: 5.0 # The maximum velocity (in mm/s) that the toolhead may travel a 90 # degree corner at. A non-zero value can reduce changes in extruder diff --git a/config/kit-voron2-2018.cfg b/config/kit-voron2-2018.cfg index 674d1ad4..b48890b7 100644 --- a/config/kit-voron2-2018.cfg +++ b/config/kit-voron2-2018.cfg @@ -216,5 +216,7 @@ max_velocity: 500 max_accel: 3000 max_z_velocity: 100 max_z_accel: 50 + +[idle_timeout] # high motor off time so I don't have to relevel gantry often -motor_off_time: 6000 +timeout: 6000 diff --git a/config/printer-anycubic-kossel-2016.cfg b/config/printer-anycubic-kossel-2016.cfg index d3c50bd1..53ef9f81 100644 --- a/config/printer-anycubic-kossel-2016.cfg +++ b/config/printer-anycubic-kossel-2016.cfg @@ -71,9 +71,11 @@ kinematics: delta max_velocity: 500 max_accel: 3000 max_z_velocity: 150 -motor_off_time: 360 delta_radius: 99.8 +[idle_timeout] +timeout: 360 + #[delta_calibrate] #radius: 50 #manual_probe: diff --git a/config/printer-anycubic-kossel-plus-2017.cfg b/config/printer-anycubic-kossel-plus-2017.cfg index 30b8c0d6..cc8b64a3 100644 --- a/config/printer-anycubic-kossel-plus-2017.cfg +++ b/config/printer-anycubic-kossel-plus-2017.cfg @@ -80,11 +80,13 @@ kinematics: delta max_velocity: 200 max_accel: 3000 max_z_velocity: 200 -motor_off_time: 360 delta_radius: 115 # if you want to DELTA_CALIBRATE you may need that #minimum_z_position: -5 +[idle_timeout] +timeout: 360 + #[delta_calibrate] #radius: 115 #manual_probe: diff --git a/klippy/extras/idle_timeout.py b/klippy/extras/idle_timeout.py new file mode 100644 index 00000000..ac91cbcf --- /dev/null +++ b/klippy/extras/idle_timeout.py @@ -0,0 +1,48 @@ +# Support for disabling the printer on an idle timeout +# +# Copyright (C) 2018 Kevin O'Connor +# +# This file may be distributed under the terms of the GNU GPLv3 license. + +DEFAULT_IDLE_GCODE = """ +M84 +""" + +class IdleTimeout: + def __init__(self, config): + self.printer = config.get_printer() + self.toolhead = None + self.last_timeout = 0. + self.idle_timeout = config.getfloat('timeout', 600., above=0.) + self.idle_script = config.get('gcode', DEFAULT_IDLE_GCODE) + def printer_state(self, state): + if state == 'ready': + self.toolhead = self.printer.lookup_object('toolhead') + reactor = self.printer.get_reactor() + reactor.register_timer(self.timeout_handler, reactor.NOW) + def run_idle_script(self): + gcode = self.printer.lookup_object('gcode') + for line in self.idle_script.split('\n'): + try: + res = gcode.process_batch(line) + except: + break + if not res: + # Raced with incoming g-code commands + return + self.last_timeout = self.toolhead.get_last_move_time() + def timeout_handler(self, eventtime): + info = self.toolhead.get_status(eventtime) + print_time = info['print_time'] + status = info['status'] + if print_time <= self.last_timeout or status == 'Printing': + return eventtime + self.idle_timeout + estimated_print_time = info['estimated_print_time'] + elapsed_time = estimated_print_time - print_time + if elapsed_time < self.idle_timeout: + return eventtime + self.idle_timeout - elapsed_time + self.run_idle_script() + return eventtime + 1. + +def load_config(config): + return IdleTimeout(config) diff --git a/klippy/toolhead.py b/klippy/toolhead.py index 94ee0891..c8a48fa8 100644 --- a/klippy/toolhead.py +++ b/klippy/toolhead.py @@ -231,11 +231,7 @@ class ToolHead: self.idle_flush_print_time = 0. self.flush_timer = self.reactor.register_timer(self._flush_handler) self.move_queue.set_flush_time(self.buffer_time_high) - # Motor off tracking - self.need_motor_off = False - self.motor_off_time = config.getfloat('motor_off_time', 600., above=0.) - self.motor_off_timer = self.reactor.register_timer( - self._motor_off_handler, self.reactor.NOW) + self.printer.try_load_module(config, "idle_timeout") # Setup iterative solver ffi_main, ffi_lib = chelper.get_ffi() self.cmove = ffi_main.gc(ffi_lib.move_alloc(), ffi_lib.free) @@ -266,7 +262,6 @@ class ToolHead: if not self.sync_print_time: return self.print_time self.sync_print_time = False - self.need_motor_off = True est_print_time = self.mcu.estimated_print_time(self.reactor.monotonic()) if est_print_time + self.buffer_time_start > self.print_time: self.print_time = est_print_time + self.buffer_time_start @@ -329,19 +324,6 @@ class ToolHead: logging.exception("Exception in flush_handler") self.printer.invoke_shutdown("Exception in flush_handler") return self.reactor.NEVER - # Motor off timer - def _motor_off_handler(self, eventtime): - if not self.need_motor_off or not self.sync_print_time: - return eventtime + self.motor_off_time - elapsed_time = self.mcu.estimated_print_time(eventtime) - self.print_time - if elapsed_time < self.motor_off_time: - return eventtime + self.motor_off_time - elapsed_time - try: - self.motor_off() - except: - logging.exception("Exception in motor_off_handler") - self.printer.invoke_shutdown("Exception in motor_off_handler") - return eventtime + self.motor_off_time # Movement commands def get_position(self): return list(self.commanded_pos) @@ -374,7 +356,6 @@ class ToolHead: for ext in kinematics.extruder.get_printer_extruders(self.printer): ext.motor_off(last_move_time) self.dwell(STALL_TIME) - self.need_motor_off = False logging.debug('; Max time of %f', last_move_time) def wait_moves(self): self._flush_lookahead() @@ -402,15 +383,17 @@ class ToolHead: return is_active, "print_time=%.3f buffer_time=%.3f print_stall=%d" % ( self.print_time, max(buffer_time, 0.), self.print_stall) def get_status(self, eventtime): - buffer_time = self.print_time - self.mcu.estimated_print_time(eventtime) + print_time = self.print_time + estimated_print_time = self.mcu.estimated_print_time(eventtime) + last_print_start_time = self.last_print_start_time + buffer_time = print_time - estimated_print_time if buffer_time > -1. or not self.sync_print_time: status = "Printing" - elif self.need_motor_off: - status = "Ready" else: - status = "Idle" - printing_time = self.print_time - self.last_print_start_time - return {'status': status, 'printing_time': printing_time} + status = "Ready" + return { 'status': status, 'print_time': print_time, + 'estimated_print_time': estimated_print_time, + 'printing_time': print_time - last_print_start_time } def printer_state(self, state): if state == 'shutdown': try: