Thoughts on homing support in Klipper

The current homing system in Klipper has become troublesome. I’ve discussed this a couple of times before (eg, Klipper development goals for 2024 ). There has been a bit of work done on the probing code, but the internal homing system is still inflexible. I’ve been working on the problem again and I wanted to describe some of the ideas I’ve been considering.

To recap, the homing system today is basically how Klipper implements the G28 command. The requirement for homing stems from the fact that stepper motors don’t track their location when powered off. So, the software needs a mechanism to determine where each carriage is prior to using a printer. The G28 command basically means “do whatever it takes to determine the carriage positions relative to the printer frame”. Unfortunately, this system in Klipper has become difficult to enhance and difficult to maintain.

I have some goals for a rework:

  • Improve support for homing with “eddy” and “loadcell” type sensors. I’d also like to remove the “probe:z_virtual_endstop” and “tmc2209_stepper_x:virtual_endstop” hacks.
  • I’d like to move the homing code into discrete “extras” modules and remove the code from “kinematic” and “core” logic.
  • It should be realistically possible for printers to not define G28 at all.
  • User facing changes should provide deprecation notices with ample time.

The rest of this post describes some ideas I have to implement the above.

@dmbutyugin , @garethky , @nickw - fyi.

Improved support for new sensors

The current homing code is focused on homing with endstops. The fundamental action of homing involves “move until endstop trigger”, “pull back”, “move again until endstop trigger”, “set Z position based on trigger time”. This logic still works well for actual endstop switches, but it doesn’t work well for other types of sensors.

For loadcell, eddy sensors, and future “eddy tap” implementations the sensing system needs more control over the movement of the toolhead and it needs to perform a detailed analysis to determine the final Z position.

In the past we’ve “shoehorned” probes and sensorless homing into pretending to be an “endstop” so that one could use the existing homing code - hence the weird endstop_pin: tmc2209_stepper_y:virtual_endstop definitions. However, even these sensors really don’t work like endstop switches. It would be preferable to use the regular probe code (including multiples samples and averaging), and it would be preferable for sensorless homing to perform different movements.

Implementation idea:

  • I’ve been considering adding a new command: HOME_Z_USING_PROBE. This command would be registered by the probe.py code. It would only be available on “cartesian like” kinematics. It would descend the toolhead until the probe triggers and it would use the probe code to determine the Z position (eg, probing_move()). The kinematic code would be changed to not require a stepper_z endstop_position nor endstop_pin. The code would use only the probing code (not any of the home_rails() code).

The main advantage of a command like this is that it can utilize the more advanced probing code and support more advanced probe types. It would be like running something like: SET_KINEMATIC_POSITION Z=9999, PROBE, SET_KINEMATIC_POSITION Z=0.202319

Move homing code out of "kinematic” and “core” logic

It’s painful to trace the code flow of a homing operation. It roughly looks like:

  • on init:
    • kinematic classes declare stepper.py:PrinterRail instances
      • stepper.py:PrinterRail reads and stores homing settings from config
        • PrinterRail creates mcu.py:MCU_endstop classes
      • toolhead.py loads the extras/homing.py module
        • extras/homing.py registers the G28 command
  • on G28:
    • homing.py:cmd_G28() calls kinematics/x.py:home()
      • which calls back into homing.py:home_rails()
        • calls rails.get_homing_info(), kin.set_position(), toolhead.move(), endstop.home_start(), …
        • but sometimes calls into extras/probe.py, extras/tmc.py, extras/homing_heaters.py, extras/z_thermal_adjust.py, extras/endstop_phase.py, extras/gcode_move.py, extras/z_thermal_adjust.py, extras/bltouch.py, extras/probe_eddy_current.py, extras/smart_effector.py
    • In addition, because an incorrect G28 can cause damage, it’s behavior is frequently modified with: extras/homing_override.py, extras/safe_z_home.py, extras/gcode_module.py

There’s something like 20 different discrete modules that get directly involved with a homing operation. Unfortunately, that means it is difficult to make changes to the code and it increases the chance that a change will break an obscure area of the code.

If we can deprecate probe:z_virtual_endstop that will help significantly. I’d also like to remove steppers.py and kinematics/*.py from the homing logic.

Implementation idea:

  • Introduce a new [home_endstop stepper_x] “extras” module that adds a new HOME_ENDSTOP CARRIAGE=stepper_x command. The config might look like:
[home_endstop stepper_x]
endstop_pin: PB3
position_endstop: -5
homing_speed: 20
  • Introduce a new [home_sensorless stepper_x] “extras” module that adds a new HOME_SENSORLESS CARRIAGE=stepper_x command. The config might look like:
[home_sensorless stepper_x]
position_endstop: -5
homing_speed: 20

These would be two distinct modules. Each would directly load and store the homing settings, and directly register their associated G-Code command. The home_endstop module would perform the standard “endstop homing” available today - move until endstop triggers, back off, move again, and set position based on trigger time. The home_sensorless module would be similar, but it would not take an “endstop_pin” and the homing procedure would be slightly different. It would back off, but not home a second time (and it may perform additional checks specific to sensorless homing). The HOME_ENDSTOP and HOME_SENSORLESS commands would always home the carriage and only just the specified carriage. It would be a low-level command as homing a carriage in the wrong situation (eg, homing Z when not over the bed) could cause damage.

There are some advantages to adding new commands. The commands can fully track their own state and not require the kinematics and core code to store/interpret homing state. The code is easier to implement and describe because it has a more defined outcome than G28 (it always means home the specific carriage using the specific procedure and it never means “figure out what carriages to home and how”). Also, it would allow passing in custom parameters to the homing operation (eg, HOME_ENDSTOP CARRIAGE=stepper_x SECOND_HOME_SPEED=10).

It should be possible to not define G28

The G28 command is a mess. In the early Reprap days it simply meant “home x”, then “home y”, then “home z”. Things are much more complicated today on nearly all mainstream printers. Many printers today could damage themselves if a G28 is issued at the wrong time. For example, a G28 Z0 on printers that use a probe could easily damage the bed. On my Voron2, I use sensorless homing for XY, and a G28 X0 without the proper preparation would likely result in an incorrect X setting which could easily lead to a damaging toolhead collision. Modules like [safe_z_home] help with this, but they also lead to uncertainty for 3rd party code - to wit, “maybe issuing a G28 Z0 will do the right thing … but maybe safe_z_home wasn’t declared and G28 Z0 will rip apart the bed…”

On my Voron2, I define a [gcode_macro HOME] macro that I use to home the printer. I always home the printer using that macro, and never issue a G28 directly. However, I still have G28 exposed because the HOME macro uses that to implement the low-level homing. I’d feel better if the low-level carriage homing wasn’t exposed as I never want to run it outside of the HOME macro.

Implementation idea:

  • Strive for a future where the core Klipper code does not define G28. Users that want a G28 can define it using a [gcode_macro G28], [safe_z_home], or some other module. Expect some users will only define a HOME type macro or perform homing directly from START_PRINT.

Don’t break everyone’s existing config

Every printer today has to home and that means basically every printer today is using G28 in some form. Obviously changing the homing system risks causing a huge disruption. Minimizing that would certainly be a priority.

I have some ideas that could potentially mitigate the disruption:

  • It may be possible to introduce some kind of hidden “legacy_G28” module that uses the existing config layout (eg, “position_endstop” in “[stepper_x]”) and registers a G28 command with the existing behavior, while invoking some kind of “HOME_XXX” logic behind the scenes. Possibly with some gentle deprecation warning suggesting that the config be updated.
  • We could deprecate [homing_override] and urge users to convert to [gcode_macro G28] (that calls HOME_XXX).
  • The [safe_z_home] module could directly register G28 (instead of overriding it). It could also gain options to use “endstop”, “sensorless”, or “probe” mechanisms for each xyz carriage.

Of course, another valid option is to not make some or all of the above changes.

The above is some high-level ideas. Nothing is “set in stone”. I wanted to write up some of the ideas I had. There has been some recent work on “generic_cartesian”, recent work on “loadcell probing”, and recent discussions on “eddy tap”. Given all this I think it would be helpful to have a feel for both long-term and short-term goals for the homing implementation in Klipper. Hopefully that’ll make it easier to get new features into Klipper in the short term in a way that also aligns with long-term thinking.

-Kevin

7 Likes

If getting rid of probe:z_virtual_endstop is going to enable probing on x/y axes (which I really want!!) I’d like to lobby for having the probe config express that somehow.

Proposal: A probe should define what axes it supports:

[probe]
axes: xyz

Printers with multiple homing & probing systems exist in the commercial world. Sometimes with multiple systems that work on the same axis. E.g.:

  • Sensorless homing
  • A load cell nozzle touch-off system
  • An eddy type bed scanner
  • A computer vision system
[home_sensorless stepper_x]
position_endstop: -5
homing_speed: 20

[home_sensorless stepper_y]
position_endstop: -5
homing_speed: 20

[load_cell_probe]
axes: xyz
...

[probe_eddy_current]
axes: z
...

[probe_computer_vision]
axes: xy
...

Another use case is having sensorless homing in both directions on an axis. Commercial machine use this to validate that the axes move to full length and that there are no obstructions or tight spots in the motion.

1 Like

I guess my proposal is already included in what @garethky sketched out:
One of my printers has a static X/Y gantry and a moving bed. Because it is belt-driven, the bed drops to Z-max when powered off.

Today, an involved mechanical solution prevents this, but it would be easier to catch this with endstops at Z-max and then use whatever probe is installed to get Z=0.

Basically, a multi-stage homing process:

  1. Get triggers for Z-max (potentially already triggered at startup).
  2. Move up towards Z-min.
  3. Get probe trigger for Z=0.

I agree that it could be useful to home x/y with loadcells in the future. (It would also be useful to probe X and Y.)

For what it is worth, I’m not sure we should have the probe.py module declare what axes it can home … so that some other software can determine which hardware modules can home each axis … so that some other software can determine what actions and hardware to use when requested to home an axis…

An alternative would be for the hardware modules capable of advanced homing to register the appropriate commands themselves. For example, loadcell.py could register a command like: LOADCELL_HOME AXIS=X X_TAP_POSITION=4.0 HOMING_SPEED=30. Users can then execute those commands (if they wish).

Similarly for probing - we may want to have the hardware modules define the commands they support (eg, LOADCELL_PROBE AXIS=X PROBING_SPEED=10).

That is a good point. It would be useful to have multiple modules that can home a single axis (the user can choose which one to invoke), and you have a good point that a [home_sensorless] type of module may want to support commands that can home in both directions.

Similarly, we should support printers with multiple Z probes, but that is a slightly different topic.

Thanks for the feedback.
-Kevin

This is an interesting direction! I see that you, Kevin, want to do a major revamp of homing in Klipper. I want to share my thoughts on this matter.

Generally, I think the idea of adding more primitive, low-level commands for homing makes a lot of sense. Unlike fiddling with various ways to override G28, these will provide a way to get the expected behavior in a predictable and guaranteed way. It will also be much easier to add new homing functionality.

That said, I have some questions about the current proposal. For example

  • It is not immediately clear to me how homing of a delta printer can be represented using the suggested primitives? Specifically, for a delta printer all 3 rails must be homed simultaneously, but they must be moved until their respective endstops trigger, and the proposed low-level commands do not support that behavior AFAICT. I think deltesian will have similar issues, not sure about other kinematics.
  • It is a little bit difficult for me to picture how features like endstop_phase, multi-mcu homing and such would fit this new architecture - such as to not introduce code deduplication and having to reimplement their support in each of the homing module (though it is probably possible, just would require some thinking).

Also, if we are doing complete revamp of the homing, I think the following features would be really nice to have (not a must, just nice to have):

  • An ability to home any stepper (or combination of steppers); for example a common request from various filament changing projects is to be able to move several steppers, including extruder, until a certain endstop triggers, something Klipper does not support today.
  • An ability to define a homing sequence using default homing primitives for obscure kinematics like deltesian and linear axis tripteron.
  • An ability to home several axes in parallel, of course when it is meaningful to do so, something that the user could configure using default homing primitives.
  • endstop_phase support on Cartesian-style kinematics other than plain Cartesian (FWIW, this would only work if all ‘entangled’ axes are homed together in the same homing sequence).

And my thoughts on ‘homing’ decomposition into building blocks:

  • The most basic low-level homing primitive is moving groups of steppers with a certain ‘overall’ speed and ramp-up acceleration, but with each stepper having its own linear coefficient, until a certain event occurs for each stepper group, or until main Klipper issues a stop even (e.g. if the max distance is traveled). In case of a simple endstop or probe homing these events would be that a certain pin is triggered, and in case of a multi-mcu, loadcell, etc. homing, it would just be the main Klipper event. FWIW, this primitive may or may not be exposed to the user.
  • Interpretation of such ‘homing move’, given the time of the event(s), stepper positions at the event(s) and their final positions, kinematics-aware.
  • Implementation of the ‘homing’ of an ‘axis’ (or groups of axes): depends on the hardware used (endstop, sensorless homing, loadcell) and must use the other two blocks above in order to organize the motion of the axis of interest as appropriate. This may or may not be exposed to the user.
  • Kinematics-aware homing of the printer axes: defines which axes/carriages can be homed and defines and implements commands to do so.
  • High-level homing routine (which axes to home and in which order), defined by the user.

So, we can still have

[home_endstop stepper_x]
endstop_pin: PB3
position_endstop: -5
homing_speed: 20

[home_sensorless stepper_y]
position_endstop: -5
homing_speed: 20

[home_loadcell stepper_z]
position_endstop: 0
homing_speed: 5
...

However the user would not typically call HOME_ENDSTOP CARRIAGE=stepper_x. Instead, the kinematic would define a command like HOME_TOOLHEAD with the arguments that would depend on the kinematics. For example, for Cartesian-style kinematics that would be HOME_TOOLHEAD CARRIAGE=<carriage_id>. For a delta, it could be HOME_TOOLHEAD [AXIS=Z], and for deltesian, it could be HOME_TOOLHEAD [AXIS=[Y|Z]]. It would also be appropriate to restrict that all carriages participating in the single homing action from kinematic perspective (e.g. all delta carriages) must use the same endstop mechanism, so that the kinematic class could just forward the execution to the appropriate class.

And we could also have a class [extruder_endstop] and extruder would define a command similar to manual_stepper like MOVE_EXTRUDER EXTRUDER=<extruder_id> [STOP_ON_ENDSTOP=[1|2|-1|-2]] ....

Alternatively, we’d have to support HOME_ENDSTOP CARRIAGE=stepper_a,stepper_b,stepper_c and alike, but I feel like here the kinematic abstraction would be leaking into the underlying endstop classes, and also a simple typo could easily ruin the day and damage the printer (e.g. if a user of a delta printer enters something like HOME_ENDSTOP CARRIAGE=stepper_a,stepper_b ,stepper_c or say prematurely accidentally hits ‘Enter’ after typing HOME_ENDSTOP CARRIAGE=stepper_a,stepper_b).

Let me know what your thoughts are.

Thanks for the feedback Dmitry.

As you may have guessed, my expectation is that delta users executing the low-level primitives would need to issue HOME_ENDSTOP CARRIAGE=stepper_a,stepper_b,stepper_c. That is, the HOME_ENDSTOP code would effectively implement the homing.py:home_rails() code today. Currently homing.py:cmd_G28() calls delta.py:home(), which calls homing.py:home_rails() which gathers information from PrinterRail() – I’m thinking in the future HOME_ENDSTOP could do that directly while gathering information from [home_endstop ...].

I have also wondered about what should happen if the user issues a HOME_ENDSTOP CARRIAGE=stepper_a,stepper_b, and I think in that case HOME_ENDSTOP should report an error. That is, I think HOME_ENDSTOP should attempt to validate the request (validate in the sense of, is it ever valid to home that set of carriages, not validate in the sense of, is it currently valid to home that set of carriages). I think HOME_ENDSTOP should be able to look at all the available carriages, check which axes those carriages move along, and then verify that requested carriages align with that set. This is a bit of “hand waving”, but I think it should be possible, and I think validation may be less trouble than trying to infer what actions to take on a G28 today.

I think this will indeed be a challenge.

At a high-level I’m not suggesting duplication of lots of code. In my proposal, HOME_ENDSTOP will have similar functionality to HOME_SENSORLESS, and I’d expect the two to be able to use similar “helper classes” to implement that functionality.

Today, homing.py:cmd_G28() declares the G28 command, it’s behavior is specialized by kinematics/x.py:home(), which is adjusted by stepper.py:get_homing_info(), which may be specialized by probe.py:_handle_home_rails_begin(), which may be specialized by bltouch.py:multi_probe_begin(). And, the whole thing may be overridden by safe_z_home.py:cmd_G28().

I think this series of overrides and specialization is not working out well. In the future, using the example above, I think that safe_z_home.py should directly register its cmd_G28 and it can use a G28Helper() class shared with other implementations. Similarly, HOME_ENDSTOP and HOME_SENSORLESS can both use a heper class similar to homing.py:HomingMove. The difference being - the current code has a kind of “split ownership” of the homing process while going forward I’d prefer more of a “direct ownership”. This is useful with hardware like the “eddy probe” (and future loadcell probes). These probes want to do something slightly different and it is really painful to “specialize” or “override” the current chain of software - but implementing an entirely different code path for them is pretty straight forward (we basically want SET_KINEMATICS Z=9999, PROBE, SET_KINEMATICS Z=...).

I have an entirely separate idea for the endstop_phase code. Today, that code looks at where an endstop triggers and compares the tmc stepper phase to past trigger points - it can then adjust the toolhead position to account for unrepeatable trigger points in an endstop switch. In the future, I think we should require users to run a new ENDSTOP_PHASE_APPLY type command, which will compare what the tmc stepper phase would be at a nominal toolhead position (eg, 0,0,0) with past stepper phases at that nominal toolhead position, and then adjust the toolhead position accordingly. This has two big advantages - it doesn’t require “endstops” (it should work with probes and other types of hardware) and it is would no longer be tied to the homing process at all. (The logic doesn’t need to be implemented using the homing point and after homing - it can be implemented with any nominal point and at the time of user request.)

I agree that is the basis of homing today, and I agree that it needs to remain available in the future. This is the core “move until trigger” code found in homing.py:homing_move(). However, I think it is important to note that this code doesn’t work well with loadcells and eddy probes. I think these sensors need to implement a different base mechanism (eg, HOME_Z_USING_PROBE).

The homing_move() code works by simultaneously moving multiple stepper_z carriages until each carriage hits its endstop and then each carriage position is determined by the trigger time. The HOME_Z_USING_PROBE code would move the toolhead along the Z axis until the probe reports a trigger position, and then the probe code determines the Z position of the toolhead by performing an analysis of events before the trigger and from toolhead movement after the trigger. It’s a subtle difference (moving carriages vs moving the toolhead; setting position based on trigger time vs setting position based on information obtained after the trigger).

I think I understand what you are saying. We may want to call that new command G28 though. That is, we will already need to implement some kind of extras/gcode_g28.py code that implements the current G28 behavior. I agree it would need to figure out what carriages to home (eg, HOME_ENDSTOP CARRIAGE=stepper_x or HOME_ENDSTOP CARRIAGE=stepper_a,stepper_b,stepper_c). This is a bit “hand wavy” and I’m unsure of the exact implementation details, but I think it should be possible. I think we could limit this new HOME_TOOLHEAD / G28 functionality to HOME_ENDSTOP (aka homing.py:home_rails()), and we could say that if someone wants the newer functionality (eg, HOME_SENSORLESS or HOME_Z_USING_PROBE) then they’ll need to convert to explicit low-level calls. Not sure. Certainly we’ll want safe_z_home.py to support a configuration that enables newer mechanisms.

FWIW, I also have an entirely separate idea for extruder homing. (Yeah, I know - lots of ideas…) The idea is to allow a [manual_stepper some_stepper] to register/unregister itself as an “extruder_stepper” with a given extruder. One could then use this mechanism to home the stepper, attach it to the extruder, detach it to home again, etc.

Separately, I have been working on “6-axis” support and as part of that I have a test branch that supports registering [manual_stepper] objects as additional “A”, “B”, “C”, etc. axes on G1 commands. So, I believe supporting “manual_stepper as extruder_stepper” should be reasonable.

There is a lot here, so let me know if I missed something or if something I’ve written doesn’t make sense.

Thanks again,
-Kevin

I agree that would be useful. The challenge here is that the “move until trigger” system today requires a trsync object to be allocated in the micro-controller for each endstop and each stepper involved in a homing event. The code today handles this by allocating the set of trsync objects for all carriages that can be simultaneously homed in the printer config.

However, if the user can home an arbitrary set of steppers to an arbitrary set of simultaneous endstops then I fear we could run into a situation where there isn’t a trsync available on the mcu and the user would get strange errors. For example, if an extruder motor is on an mcu by itself then no trsync object would be allocated on that mcu. Similarly, if four stepper_z objects were on an mcu by itself then only one trsync object would be allocated and it would not be possible to home two of those stepper_z objects to one endstop while the other two simultaneously homed to some other endstop.

There may be a technical solution to the above. Not sure. Another option may be to have the user specify in the config which homing combinations they wish to support (eg, via the [manual_stepper my_homing_extruder_motor] system I mentioned above).

Cheers,
-Kevin

Hi Kevin, some additional comments below, and thanks for taking a time to read through my thoughts.

Sorry if this was not apparent, I actually did not intend to say that this has to be fully configurable at runtime. It is fine if homing configurations have to be configured in the config file. Just for example that now there is no way to ‘home’ extruder steppers, that is, to move them until a certain pin flips. Manual steppers cannot be moved together like this, only one at a time (and then there is a separate implementation for their homing that currently does not support dripping moves).

My main concern is that then all these commands (HOME_ENDSTOP, HOME_SENSORLESS, HOME_Z_USING_PROBE) must be kinematics-aware, so I suspect that the complexity will just be pushed elsewhere, plus duplicated a few times (unless we are able to find the right abstractions).

OK, that’s actually a very neat idea.

Actually, that code does not appear to be that low-level primitive, as it actually makes a lot of actions and has a lot of assumptions. As a building block, I thought about more like a ‘dripping move’ that currently is split between the Toolhead class, and then some C and low-level MCU code. If we were to factor this code out of the Toolhead class entirely, and simply have it accept just the parameters like (distance, velocity, acceleration, list([(stepper, coeff)]), stop_signal), then I think it could go a long way in simplifying and unifying the homing logic between many subsystems (but that’s just my opinion), even if the list of acceptable steppers can be from some predefined set.

FWIW, I know too little about the details of homing using eddy probe or a loadcell, so I may say complete nonsense here. But my expectation is that that the homing process requires to bring the toolhead into the close proximity to the bed based on the readings of external sensor (eddy current sensor, or loadcell amplifier), and then either just do the measurements and set the toolhead position (eddy sensor), or move the toolhead further until a certain distance or another threshold from the amplifier is reached, then retract, and calculate the position. So, in all these cases the low-level facility like I described above can be used here. Just that if the stop_signal is the endstop on the same MCU, the MCU code can take shortcuts when checking for the stop signal, however for a device like loadcell, the measurements have to be processed by the main Klipper code first and then sent over to the MCU when it is time to stop.

It still means that the main extruder stepper (not the additional ‘manual’ one) must be able to move until the endstop triggers (so bringing back the argument that this requires an appropriate trsync object allocation).

Can we then maybe have similar to generic_cartesian

[manual_carriage a]
...

[stepper sa1]
carriages: a
...

[stepper sa2]
carriages: a
...

? :smiley:

FWIW: load_cell_endstop.c we have to make the decision to stop on the MCU. If for no other reason than the buffering in sensor_bulk, it cant be on the Host.

Edit: Maybe eddy current sensors can get away with doing it on the Host because they are non-contact and have a lot of “runway” before a collision. But load cells are post-collision detection, so every millisecond counts.

Ah, okay. I agree that we want to make the printer.cfg more flexible in configuring additional homing operations.

I also agree that “manual_stepper” should be enhanced to support “drip homing”.

I missed the requirement for multiple extruder_steppers to home as a group. That is, indeed, an important consideration. Thanks.

I am similarly concerned.

My current thinking (which is still fluid) is that HOME_ENDSTOP is basically homing.py:home_rails(), HOME_SENSORLESS would be a new and slightly different “home_rails()” that still relies on the same “move until trigger” system, and that HOME_Z_USING_PROBE would replace “home_rails()” with probe hardware specific code.

So, not necessarily kinematics aware (or at least not necessarily more aware than the current code).

Okay, I missed that. That is an interesting idea. I’ll have to give that some additional thought.

Some initial impressions:

  • It would be a nice way of fixing rotary delta homing and making scara kinematics easier to implement (these kinematics want constant stepper speeds during homing, which do not align well with moves using the normal kinematic system)
  • The current code has a single “move until trigger” system (homing.py:homing_move()) for both G28 and common PROBE commands. If we introduce an alternative “move steppers at coeff speed until trigger” system we would still need to retain the current “kinematic move until trigger” for common PROBE commands. This is a requirement for rotary delta (where constant Z speed moves are not constant stepper speed moves) and is generally required on all kinematics in that probe moves should be validated within the kinematic checks. It would also be a requirement for any future “probe x axis” support. It would also mean that both systems would need to be “drip move” capable.
  • Using a different “move until trigger / drip move” system may be orthogonal to refactoring of G28.

It is an interesting idea and I need to give it some additional thought.

I’m not sure I understand what you are saying. Yes, all of these systems do currently utilize the same “move until trigger” primitive (homing.py:homing_move()). What isn’t working is trying to share the homing.py:home_rails() code. I fear it has become too painful to try to further specialize or override the homing system to fix that, and that refactoring may be needed.

As above, I do think there is a subtle difference between “kinematic move until trigger” (what a probe wants) and “constant speed stepper move until trigger” (what a homing operation can use).

As Gareth mentions, the “trigger” for both eddy and loadcells are a trsync signal generated by an mcu. It needs this to obtain the rapid halt of the steppers. The final position on eddy/loadcell is not however the time of that trigger - it’s a separate analysis that is probe hardware specific and may involve moving the toolhead and may involve an arbitrary amount of additional time (which is why they don’t work well with homing.py:home_rails()).

FYI, the current Klipper code does not require defining a stepper in the [extruder] config section. One may rely entirely on associating extruder_stepper objects at run-time.

It’s a good point about [manual_stepper] - should it be treated as a “stepper” or a “carriage”? Should we define a new [manual_carriage]? We can certainly add additional capabilities. Not sure.

Thanks again,
-Kevin

OK, thanks for the correction! But in general this does not change my point in that the probing/homing moves can be implemented using proposed facilities.

Right. For homing when the position of the stepper is unknown, there is no reasonable way to perform homing other than just moving the stepper at a constant speed (besides the initial ramp-up), as there is no way to reason why the stepper should be generally accelerating or decelerating during homing. For probing, however, admittedly this is not the case. I’d still implement the ‘dripping move’ until trigger event outside of a Toolhead class and try to share this implementation between different cases (at least, give it a thought). For example, it may still accept a Move as an input, and for a Cartesian-style carriage the homing and probing moves would match that axis, and on kinematics/carriages where the homing move does not match any linear motion, we could just override the steppers calc_position_cb during the dripping move with cart_stepper_x_calc_position (maybe again with some linear coefficient) and just pass an artificially constructed Move with just the fake x component.

I think we are on the same page here. My proposal is to implement a dripping move in some form outside of a Toolhead class and use it as a building block for all types of homing and probing moves. I agree that homing.py:homing_move() is too complicated and has built-in assumptions about the homing moment and calculating the toolhead position (so the primitive should instead just return the trigger and stop stepper positions and stop time). Then, my other point was that HOME_ENDSTOP, HOME_SENSORLESS, HOME_Z_USING_PROBE will be kinematics aware (and in fact more aware than the current code) - because right now it is the kinematics class that issues rail/carriage homing, so it makes sure not to pass invalid configurations to homing.py:home_rails, so it knows nothing about printer kinematics, and at the moment each kinematics class must worry about only this particular kinematics. But I think we may just need to give it a try and see, maybe some good ideas will appear how to implement HOME_ENDSTOP, etc. in the reasonably kinematic-agnostic way.

Well, for additional axes it makes sense to have an option to define a multi-rail. For the generic_cartesian kinematics it makes sense to add [manual_carriage] configuration section, as it fits nicely with other sections. How should it be in other kinematics, I do not know, maybe the same, or maybe just follow the general convention for the multi-rails as [manual_stepper a], [manual_stepper a1], … for consistency with the rest of the config. In general, I can see the extra axes being useful on various robot-like kinematics, like polar, scara, etc. FWIW, I recently saw this kind of 4 axis polar printer live, and at the moment it has to use RRF because Klipper does not support this kind of kinematics (and for that matter, the upcoming generic_cartesian kinematics is not going to help either), but I think it would be great to keep those kinds of hybrid kinematics in mind. And perhaps later introduce something like generic kinematics to Klipper, that would be similar to generic_cartesian, but allow defining carriages associated not just with cartesian axes, but with other styles of kinematics. Just an aspirational and inspirational idea:

[printer]
kinematics: generic

[carriage phi]
kinematics: polar_a

[carriage r]
kinematics: polar_r

[carriage z]
kinematics: cartesian_z

[manual_carriage a]
...

[stepper z]
carriages: z

[stepper ra1]
carriages: r-a

[stepper ra2]
carriages: r+a

though this derails the homing discussion by quite a bit :slight_smile:

1 Like

I have a similar setup with a Voron v0 with a slideswipe for klicky probe.
Mechanical constraints are: slideswipe can be deployed only if the bed is at least at Z=30.

So I installed the Z endstop switch at Z=120 ala v0.2 style and it basically defines the homing position. for starting heatsoak I don’t want the bed at Z=120, so I have to home the printer before starting heatsoak, to set the kinematic position. at print start I do a probe that adjusts the gcode offset for the print.

So in the language of requirements, I have two endstops on the Z axis, one at z_max one at probe_z_offset. The second one can be safely used only after the first one has triggered.

The other tangential requirement for detachable probes is, if a printer starts with the probe attached, the available safe space for movement is changed and the printer should be able to home enough to stow the probe and then home properly. Macros for this were not fun, and there is still a failure mode because a triggered detachable probe is quite indistinguishable from a not attached probe. This one may be a requirement for the probe, and not for the homing.

I’d like to see “near” end stops. Home at full speed until the “near” end stop is hit and then bring it on in slow at the homing speed. A bunch of clowns made fun of this when I first suggested it a while back, usually claiming it was not needed since you could start homing before bed/hotend heating was started, so no great time savings, but there are many use cases where you don’t have that time to kill. “You can never make it go fast enough” is my motto. Would come in very handy when doing lots of start print ops while testing early stage things.

Agreed.

Agreed. This is how the scara forks implemented homing.

I agree it would be great to improve the “drip move” system. I don’t immediately have a good idea for how to do that though. If you’ve got some ideas that would be great - what kind of API would the external “dripping move” class have, and what kind of API would it require from the toolhead class?

Agreed. There are risks, but I think it is worth exploring. I feel the current homing code has become too complex.

FWIW, another possibility is to continue to use [manual_stepper my_stepper] and add commands to associate it with an extruder_stepper, kinematic stepper, another manual_stepper (for simultaneous homing), or a g-code axis identifier. It’d move the complexity from printer.cfg to run-time, but for unusual printers that may have some benefits.

I think the next step on my side is to tag another release of Klipper. There’s a lot of stuff coming up and I think it would make sense to tag the release prior to those changes.

-Kevin

Thank you, Kevin.

I’ll try to see if I can think of something in the coming weeks.

FWIW, it might be useful to let any stepper act as a manual_stepper. This, of course, does not exclude the benefits of letting manual_stepper to be joined with other steppers. But to be honest, I do not have a very clear picture how to define, say, new manual axes in printer.cfg for existing kinematics. I do think it would make sense to have [manual_carriage] for generic_cartesian, but for others it may look out of place, especially if we are going to deprecated [dual_carriage]. But I think we can discuss this separately.

That makes sense, and as I wrote in the PR for generic_cartesian kinematics, given that a complete revamp of homing is planned that affects all kinematics, it probably makes sense to commit it with the current implementation of homing as-is (after the Klipper release, that is), and then deal with homing changes there alongside other kinematics.

Just want to note that right now both “probe.py” and “bed_mesh.py” rely on a singular ‘probe’ object, and they’re coupled enough to make changes difficult.

I’ve done some quick hacks to support multiple probes in “probe.py”, but I think this is also going to be needed:

[probe some_name]
...

[bed_mesh]
probe: some_name

Edit:
Another thought when it comes to steppers.

Right now things look like:

[stepper_x]
[stepper_y]
[stepper_z]
[stepper_z1]
...
[printer]
kinematics: cartesian
...

[stepper_z1] feels kind of hacky. It works, but if things are being changed this is an area for improvement.

What about this instead?

[stepper my_x_stepper]
[stepper another_x_stepper]
[stepper y]
[stepper random_name1]
[stepper random_name2]
...
[printer]
kinematics: cartesian
stepper_x: my_x_stepper, another_x_stepper
stepper_y: y
stepper_z: random_name1, random_name_2
1 Like