From dd94441e7405b9a8f08c9493ab1b1f9a9f693acf Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sun, 1 Feb 2026 16:41:01 -0500 Subject: [PATCH 1/3] configfile: Only warn once for each message sent to runtime_warning() Signed-off-by: Kevin O'Connor --- klippy/configfile.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/klippy/configfile.py b/klippy/configfile.py index 8210de2ba..28c01ff39 100644 --- a/klippy/configfile.py +++ b/klippy/configfile.py @@ -468,8 +468,9 @@ class PrinterConfig: self.autosave = ConfigAutoSave(printer) self.validate = ConfigValidate(printer) self.deprecated = {} - self.runtime_warnings = [] self.deprecate_warnings = [] + self.runtime_warned = {} + self.runtime_warnings = [] self.status_raw_config = {} self.status_warnings = [] def get_printer(self): @@ -497,10 +498,13 @@ class PrinterConfig: self.validate.check_unused(config.fileconfig) # Deprecation warnings def runtime_warning(self, msg): + if msg in self.runtime_warned: + return logging.warning(msg) res = {'type': 'runtime_warning', 'message': msg} self.runtime_warnings.append(res) self.status_warnings = self.runtime_warnings + self.deprecate_warnings + self.runtime_warned[msg] = True def deprecate(self, section, option, value=None, msg=None): key = (section, option, value) if key in self.deprecated and self.deprecated[key] == msg: From 4fb3b24c8da1ee04be6cdfb705b86c463e1c74bf Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sun, 1 Feb 2026 17:07:55 -0500 Subject: [PATCH 2/3] manual_stepper: Rework STOP_ON_ENDSTOP parameter Replace the integer values of STOP_ON_ENDSTOP with string values and deprecate the older format. The newer string values should make the commands easier to understand and allow for more homing options in the future. Signed-off-by: Kevin O'Connor --- config/sample-mmu2s-diy.cfg | 6 ++--- docs/Config_Changes.md | 6 +++++ docs/G-Codes.md | 43 +++++++++++++++++++++------------ klippy/extras/manual_stepper.py | 19 ++++++++++++--- 4 files changed, 52 insertions(+), 22 deletions(-) diff --git a/config/sample-mmu2s-diy.cfg b/config/sample-mmu2s-diy.cfg index 41fe669f9..c4634761c 100644 --- a/config/sample-mmu2s-diy.cfg +++ b/config/sample-mmu2s-diy.cfg @@ -506,7 +506,7 @@ gcode: {% if printer["gcode_macro SELECT_TOOL"].tool_selected|int != -1 %} M118 Loading filament to PINDA ... MANUAL_STEPPER STEPPER=gear_stepper SET_POSITION=0 - MANUAL_STEPPER STEPPER=gear_stepper MOVE={printer["gcode_macro VAR_MMU2S"].pinda_load_length} STOP_ON_ENDSTOP=2 + MANUAL_STEPPER STEPPER=gear_stepper MOVE={printer["gcode_macro VAR_MMU2S"].pinda_load_length} STOP_ON_ENDSTOP=try_home MANUAL_STEPPER STEPPER=gear_stepper SET_POSITION=0 MANUAL_STEPPER STEPPER=gear_stepper MOVE=10 IS_FILAMENT_IN_PINDA @@ -579,7 +579,7 @@ gcode: M118 Unloading filament from extruder to PINDA ... MANUAL_STEPPER STEPPER=gear_stepper SET_POSITION=0 {% if printer["gcode_macro VAR_MMU2S"].enable_5in1 == 0 %} - MANUAL_STEPPER STEPPER=gear_stepper MOVE=-{printer["gcode_macro VAR_MMU2S"].bowden_unload_length} SPEED=120 ACCEL=80 STOP_ON_ENDSTOP=-2 + MANUAL_STEPPER STEPPER=gear_stepper MOVE=-{printer["gcode_macro VAR_MMU2S"].bowden_unload_length} SPEED=120 ACCEL=80 STOP_ON_ENDSTOP=try_inverted_home IS_FILAMENT_STUCK_IN_PINDA {% else %} MANUAL_STEPPER STEPPER=gear_stepper MOVE=-{printer["gcode_macro VAR_MMU2S"].bowden_unload_length} SPEED=120 ACCEL=80 @@ -792,7 +792,7 @@ gcode: {% if printer["gcode_macro VAR_MMU2S"].enable_5in1 == 0 %} M118 Homing selector MANUAL_STEPPER STEPPER=selector_stepper SET_POSITION=0 - MANUAL_STEPPER STEPPER=selector_stepper MOVE=-76 STOP_ON_ENDSTOP=1 + MANUAL_STEPPER STEPPER=selector_stepper MOVE=-76 STOP_ON_ENDSTOP=home MANUAL_STEPPER STEPPER=selector_stepper SET_POSITION=0 {% endif %} MANUAL_STEPPER STEPPER=idler_stepper MOVE=0 diff --git a/docs/Config_Changes.md b/docs/Config_Changes.md index cf2d53fd9..0304c2a9b 100644 --- a/docs/Config_Changes.md +++ b/docs/Config_Changes.md @@ -8,6 +8,12 @@ All dates in this document are approximate. ## Changes +20260207: The `MANUAL_STEPPER` G-Code command `STOP_ON_ENDSTOP` +parameter has changed. See the +[MANUAL_STEPPER](G-Codes.md#manual_stepper) documentation for +details. Using the previously supported integer values (-2, -1, 1, 2) +is deprecated and will be removed in the near future. + 20260109: The status value `{printer.probe.last_z_result}` is deprecated; it will be removed in the near future. Use `{printer.probe.last_probe_position}` instead, and note that this new diff --git a/docs/G-Codes.md b/docs/G-Codes.md index 6869cbc62..a186a4e71 100644 --- a/docs/G-Codes.md +++ b/docs/G-Codes.md @@ -989,22 +989,33 @@ enabled. #### MANUAL_STEPPER `MANUAL_STEPPER STEPPER=config_name [ENABLE=[0|1]] -[SET_POSITION=] [SPEED=] [ACCEL=] [MOVE= -[STOP_ON_ENDSTOP=[1|2|-1|-2]] [SYNC=0]]`: This command will alter the -state of the stepper. Use the ENABLE parameter to enable/disable the -stepper. Use the SET_POSITION parameter to force the stepper to think -it is at the given position. Use the MOVE parameter to request a -movement to the given position. If SPEED and/or ACCEL is specified -then the given values will be used instead of the defaults specified -in the config file. If an ACCEL of zero is specified then no -acceleration will be performed. If STOP_ON_ENDSTOP=1 is specified then -the move will end early should the endstop report as triggered (use -STOP_ON_ENDSTOP=2 to complete the move without error even if the -endstop does not trigger, use -1 or -2 to stop when the endstop -reports not triggered). Normally future G-Code commands will be -scheduled to run after the stepper move completes, however if a manual -stepper move uses SYNC=0 then future G-Code movement commands may run -in parallel with the stepper movement. +[SET_POSITION=] [SPEED=] [ACCEL=] [MOVE=] +[SYNC=0]]`: This command will alter the state of the stepper. Use the +ENABLE parameter to enable/disable the stepper. Use the SET_POSITION +parameter to force the stepper to think it is at the given +position. Use the MOVE parameter to request a movement to the given +position. If SPEED and/or ACCEL is specified then the given values +will be used instead of the defaults specified in the config file. If +an ACCEL of zero is specified then no acceleration will be +performed. Normally future G-Code commands will be scheduled to run +after the stepper move completes, however if a manual stepper move +uses SYNC=0 then future G-Code movement commands may run in parallel +with the stepper movement. + +`MANUAL_STEPPER STEPPER=config_name [SPEED=] [ACCEL=] +MOVE= STOP_ON_ENDSTOP=`: If STOP_ON_ENDSTOP is +specified then the move will end early if an endstop event occurs. The +`STOP_ON_ENDSTOP` parameter may be set to one of the following values: + +* `home`: The movement will stop when the endstop reports triggered + and the final position of the manual_stepper will be set such that + the trigger position matches the position specified in the `MOVE` + parameter. +* `inverted_home`: As above, however, the movement will stop when the + endstop reports it is in a non-triggered state. +* `try_home`, `try_inverted_home`: As above, but no error will be + reported if the movement fully completes without an endstop event + stopping the move early. `MANUAL_STEPPER STEPPER=config_name GCODE_AXIS=[A-Z] [LIMIT_VELOCITY=] [LIMIT_ACCEL=] diff --git a/klippy/extras/manual_stepper.py b/klippy/extras/manual_stepper.py index 175f20915..7d19b97a2 100644 --- a/klippy/extras/manual_stepper.py +++ b/klippy/extras/manual_stepper.py @@ -102,14 +102,27 @@ class ManualStepper: self.do_set_position(setpos) speed = gcmd.get_float('SPEED', self.velocity, above=0.) accel = gcmd.get_float('ACCEL', self.accel, minval=0.) - homing_move = gcmd.get_int('STOP_ON_ENDSTOP', 0) - if homing_move: + homing_move = gcmd.get('STOP_ON_ENDSTOP', None) + if homing_move is not None: + old_map = {'-2': 'try_inverted_home', '-1': 'inverted_home', + '1': 'home', '2': 'try_home'}.get(homing_move) + if old_map is not None: + pconfig = self.printer.lookup_object('configfile') + pconfig.runtime_warning("integer values for manual_stepper" + " STOP_ON_ENDSTOP is deprecated") + homing_move = old_map + is_try = homing_move.startswith('try_') + homing_move = homing_move[is_try*4:] + is_inverted = homing_move.startswith('inverted_') + homing_move = homing_move[is_inverted*9:] + if homing_move != "home": + raise gcmd.error("Unknown STOP_ON_ENDSTOP request") movepos = gcmd.get_float('MOVE') if ((self.pos_min is not None and movepos < self.pos_min) or (self.pos_max is not None and movepos > self.pos_max)): raise gcmd.error("Move out of range") self.do_homing_move(movepos, speed, accel, - homing_move > 0, abs(homing_move) == 1) + not is_inverted, not is_try) elif gcmd.get_float('MOVE', None) is not None: movepos = gcmd.get_float('MOVE') if ((self.pos_min is not None and movepos < self.pos_min) From 0dccc7e02d67d2ac6398dca5d9e88426c3af8f02 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sun, 1 Feb 2026 17:47:55 -0500 Subject: [PATCH 3/3] manual_stepper: Support STOP_ON_ENDSTOP=probe Support moving a manual_stepper until a trigger event, without resetting the final position. Signed-off-by: Kevin O'Connor --- docs/G-Codes.md | 11 ++++++----- klippy/extras/homing.py | 8 +++++--- klippy/extras/manual_stepper.py | 13 +++++++------ 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/docs/G-Codes.md b/docs/G-Codes.md index a186a4e71..cd8d745b6 100644 --- a/docs/G-Codes.md +++ b/docs/G-Codes.md @@ -1007,15 +1007,16 @@ MOVE= STOP_ON_ENDSTOP=`: If STOP_ON_ENDSTOP is specified then the move will end early if an endstop event occurs. The `STOP_ON_ENDSTOP` parameter may be set to one of the following values: +* `probe`: The movement will stop when the endstop reports triggered. * `home`: The movement will stop when the endstop reports triggered and the final position of the manual_stepper will be set such that the trigger position matches the position specified in the `MOVE` parameter. -* `inverted_home`: As above, however, the movement will stop when the - endstop reports it is in a non-triggered state. -* `try_home`, `try_inverted_home`: As above, but no error will be - reported if the movement fully completes without an endstop event - stopping the move early. +* `inverted_probe`, `inverted_home`: As above, however, the movement + will stop when the endstop reports it is in a non-triggered state. +* `try_probe`, `try_inverted_probe`, `try_home`, `try_inverted_home`: + As above, but no error will be reported if the movement fully + completes without an endstop event stopping the move early. `MANUAL_STEPPER STEPPER=config_name GCODE_AXIS=[A-Z] [LIMIT_VELOCITY=] [LIMIT_ACCEL=] diff --git a/klippy/extras/homing.py b/klippy/extras/homing.py index 723648c11..918f37e15 100644 --- a/klippy/extras/homing.py +++ b/klippy/extras/homing.py @@ -249,16 +249,18 @@ class PrinterHoming: gcode = self.printer.lookup_object('gcode') gcode.register_command('G28', self.cmd_G28) def manual_home(self, toolhead, endstops, pos, speed, - triggered, check_triggered): + probe_pos, triggered, check_triggered): hmove = HomingMove(self.printer, endstops, toolhead) try: - hmove.homing_move(pos, speed, triggered=triggered, - check_triggered=check_triggered) + epos = hmove.homing_move(pos, speed, probe_pos=probe_pos, + triggered=triggered, + check_triggered=check_triggered) except self.printer.command_error: if self.printer.is_shutdown(): raise self.printer.command_error( "Homing failed due to printer shutdown") raise + return epos def probing_move(self, mcu_probe, pos, speed): endstops = [(mcu_probe, "probe")] hmove = HomingMove(self.printer, endstops) diff --git a/klippy/extras/manual_stepper.py b/klippy/extras/manual_stepper.py index 7d19b97a2..9151bcc8f 100644 --- a/klippy/extras/manual_stepper.py +++ b/klippy/extras/manual_stepper.py @@ -78,7 +78,8 @@ class ManualStepper: self.motion_queuing.note_mcu_movequeue_activity(self.next_cmd_time) if sync: self.sync_print_time() - def do_homing_move(self, movepos, speed, accel, triggered, check_trigger): + def do_homing_move(self, movepos, speed, accel, + probe_pos, triggered, check_trigger): if not self.can_home: raise self.printer.command_error( "No endstop for this manual stepper") @@ -87,7 +88,8 @@ class ManualStepper: endstops = self.rail.get_endstops() phoming = self.printer.lookup_object('homing') phoming.manual_home(self, endstops, pos, speed, - triggered, check_trigger) + probe_pos, triggered, check_trigger) + self.sync_print_time() cmd_MANUAL_STEPPER_help = "Command a manually configured stepper" def cmd_MANUAL_STEPPER(self, gcmd): if gcmd.get('GCODE_AXIS', None) is not None: @@ -115,14 +117,15 @@ class ManualStepper: homing_move = homing_move[is_try*4:] is_inverted = homing_move.startswith('inverted_') homing_move = homing_move[is_inverted*9:] - if homing_move != "home": + if homing_move not in ["probe", "home"]: raise gcmd.error("Unknown STOP_ON_ENDSTOP request") + is_probe = (homing_move == "probe") movepos = gcmd.get_float('MOVE') if ((self.pos_min is not None and movepos < self.pos_min) or (self.pos_max is not None and movepos > self.pos_max)): raise gcmd.error("Move out of range") self.do_homing_move(movepos, speed, accel, - not is_inverted, not is_try) + is_probe, not is_inverted, not is_try) elif gcmd.get_float('MOVE', None) is not None: movepos = gcmd.get_float('MOVE') if ((self.pos_min is not None and movepos < self.pos_min) @@ -220,8 +223,6 @@ class ManualStepper: drip_completion) # Clear trapq of any remaining parts of movement self.motion_queuing.wipe_trapq(self.trapq) - self.rail.set_position([self.commanded_pos, 0., 0.]) - self.sync_print_time() def get_kinematics(self): return self def get_steppers(self):