Reviving the dockable_probe

Hi @cloakedcode, still got a little bit of fine tuning to do with toolhead placement and movement but in essence there doesn’t seem to be any hic-up’s with the code so far. Thanks again for your efforts and hope to see it merged into Klipper :crossed_fingers:

1 Like

Hi @cloakedcode is there any chance of incorporating the “Probe_Lock” etc or has it implemented in some other manner. At the moment I’m trying to home all, Z-tilt, home Z and then bed-mesh and as it stands I’m getting a long of unneeded movement, docking and undocking. I see in the code there is something about Multi_Off, Multi_First, Multi_On but not to sure what that’s about! I look forward to hearing from you.

Good news! The feature you want is already supported. See the SET_DOCKABLE_PROBE command and g-code usage example:

Thanks for that, guess I must of over looked it :face_with_spiral_eyes:

Ok so I had time to finally test it with the Euclid. This is my config:

[safe_z_home]
home_xy_position: 166,143
z_hop: 15

[dockable_probe]
pin: PC5 # Probe
x_offset: 3
y_offset: 74
samples: 3
sample_retract_dist: 3
samples_tolerance_retries: 3
samples_tolerance: 0.04
lift_speed: 20
attach_speed: 200
detach_speed: 200
travel_speed: 200
samples_result: median
# dockable probe stuff
approach_position: 309, 245
dock_position: 309, 285
detach_position: 309, 245
z_hop: 30
check_open_attach: true
z_offset = 9.150

[gcode_macro MOVE_TO_APPROACH_PROBE]
rename_existing: BASE_MOVE_TO_APPROACH_PROBE
gcode:
  SET_SERVO SERVO=probe ANGLE=175
  BASE_MOVE_TO_APPROACH_PROBE

[gcode_macro MOVE_TO_DETACH_PROBE]
rename_existing: BASE_MOVE_TO_DETACH_PROBE
gcode:
  BASE_MOVE_TO_DETACH_PROBE
  SET_SERVO SERVO=probe ANGLE=0

[gcode_macro MOVE_TO_EXTRACT_PROBE]
rename_existing: BASE_MOVE_TO_EXTRACT_PROBE
gcode:
  G1 X258 Y285

[gcode_macro MOVE_TO_INSERT_PROBE]
rename_existing: BASE_MOVE_TO_INSERT_PROBE
gcode:
  G1 X258 Y285

[servo probe]
pin: PB6
initial_angle: 0

It works, with some caveats:

  • When doing Z Tilt or Bed Mesh, the head will move to the beginning position for those (like, the first point to be probed for tilt or bed) and then will start the sequence to attach the probe. I guess that can probably be changed if doing those things in sequence with the lock option so it doesn’t detach/attach constantly, but since I was testing each step separately it did that.
  • For some reason, one of the move speeds is always slow, even me having changed the speeds on the config. You can check this video to see what I am talking about NVM I just need to add the F speed to the custom INSERT/EXTRACT probe macros so it can move faster.

Unfortunately, I am preparing to change my probe from an euclid to a beacon3d, so I won’t be using a dockable probe for long to keep helping with the test. Will probably still have it until a couple of weeks tho.

Was about to test Dockable_Probe, but I’ve been overlooking this a couple times before I realize : “Other configurations may have the dock mounted next to the printer bed so that the Z position must be known prior to attaching the probe. In this configuration the Z axis parameter must be supplied, and the Z axis must be homed prior to attaching the probe.”

This makes no sense in my specific case. Here’s my design, where there’s no need to first home Z before attaching the probe ; The dock is a tiny “bucket”. The head just has to be lifted by a arbitrary amount, then X-homed, moved above the dock, and gently moved down until the probe jumps up and closes the circuit (NC switch). It happens at ±10mm. All moves tested good (and noted) using the jog buttons, docking and undocking.

Have a look to the design ; it’s a homebrew bed slinger, based on an old Tevo Tornado (kept the frame and the bed). The first pic shows the probe in its dock. The dock is attached to the vertical extrusion, near to the bed. The second pic shows the probe attached. (the X endstop and the X stepper are on the right side, the thing that looks like a pancake motor is the tensioner).

Detaching the probe after homing all axis is just a matter of moving the probe back in the dock, and moving right to X = 0.

I think there is no simpler design, and it is optimal from this point of view (of course the printer has to be designed for !).

But will Dockable_Probe detect the pin change (HIGH to LOW, on a pulled up input) and consider the probe is attached, ready for homing all axis and do its job ?

(will test tomorrow anyway… I have a workaround, but it implies some external electronics - gates or microcontroller and a GPIO pin set as output)

With this specific setup, the probe can be attached without first homing Z… And even detached : just needs probing the bottom of the dock !

(designed that before I realize Klipper is not Marlin, and I will not be able to trigger interrupts on signal edges, doing port manipulation, nor switching back and forth between rising and falling edges :sob: )

Cool design! Quite ingenious but it might be tricky with Klipper, as you’ve noticed.

The Dockable_Probe code will not detect this as the code is basically a series of glorified (absolute) move commands. It has no concept of moving in repetitive, incremental steps until a state changes. It could do the detaching once the toolhead position is known but the tricky part is attaching the probe and stopping the toolhead without crashing.
If I were you, I’d look at what can be done with a custom ‘MOVE_TO_DOCK_PROBE’ macro to create a “move Z -0.5mm, check for probe attach, repeat if necessary” algorithm. The other macros should do the approach and detach positioning correctly if configured. However, Klipper might fight as it could refuse to make some movements if the endstops have not yet been triggered (i.e toolhead limits are unknown).

What’s really missing is a “run g-code X when endstop state changes” feature in Klipper but that’s outside the scope of Dockable_Probe by a long way.

Let me know how you get on and good luck!

This is the things I first tested…

  • if the probe is defined for a NC contact I get an error “probe is already triggered” or ssimilar, and nothing happens (no move is possible) ; tested in the hope Klipper would trigger a probe event on a falling edge. Fail.
  • if the probe is defined for a NO contact, the vertical move stops as soon as the probe is attached, but then in cannot be redefined as NC at runtime. Fail

But an external circuit can do it.

When the docked status is detected (and the machine stops). At this moment, the electrical probe signal can be inverted (passed through a NOT gate, with wired or software logic). The “do invert” signal can be emmited using a macro, by any GPIO pin (RasPi or printer mobo), to an IC (MOSFET gates or BluePill for example) that can make the probe NO or NC as needed. I’ve been thinking of doing this before I discovered your plugin. Not being familiar with Pyhton and Klipper, I’ve been thinking that the docked status was detected by closing the circuit, the plugin polling for needed pin state, opened or closed…

Maybe it could be done without any action from Klipper plugin, with just a dock sensor that inverts the signal as soon as the probe jumps to its attached position…

Using SET_KINEMATIC_POSITION and G91 makes it possible to attach the probe. (this is how I first tested the Klipper behaviour). And then it becomes more complicated… But after all, some folks use servos !

I will definitely test overriding all macros, and test them one by one. And of course I will keep you informed.

1 Like

Was able to override MOVE_TO_APPROACH_PROBE, easy…

But I’m stuck on MOVE_TO_EXTRACT_PROBE and the loop.

[gcode_macro MOVE_TO_EXTRACT_PROBE]

rename_existing: BASE_MOVE_TO_EXTRACT_PROBE

gcode:
  QUERY_DOCKABLE_PROBE
  {% for z in range(300) if printer.dockable_probe.last_status != 1 %} ; while status != 1 (! ATTACHED)
    FORCE_MOVE STEPPER=stepper_z DISTANCE=-1 VELOCITY=10 ; move down 1mm ; TODO : VELOCITY : stepper_z.homing_speed
    QUERY_DOCKABLE_PROBE
    RESPOND MSG="printer.dockable_probe.last_status : "{printer.dockable_probe.last_status}
  {% endfor %}

It does not work because printer.dockable_probe.last_status never gets updated. If I understand correctly, it is read only once, when entering the macro, and never gets read again (evaluated as a constant). Already faced the problem while creating my auto Z offset macros : had to make macros inceptions. But there were no loops !

As a result the loop can’t break.

QUERY_DOCKABLE_PROBE shows the actual status in the terminal (docked or attached), real time, but echoing printer.dockable_probe.last_status shows it never changes in the macro. I know it is the expected behaviour, but it is a severe limitation !

Stuck !

[EDIT] I’ve been a C/C++ guy for nearly 40 years (from Turbo C to Visual Studio, and never was able to code with anything else ; basic, pascal, forth, lisp, python, c#, java or whatever, I just can’t ; I tried but always went back to C/C++. It’s a severe disability !

[EDIT 2]

Was able to do something with this code :

[gcode_macro MOVE_TO_APPROACH_PROBE]

rename_existing: BASE_MOVE_TO_APPROACH_PROBE

gcode:
  
  RESPOND MSG="MOVE_TO_APPROACH_PROBE"

  G28 X      ; Z-hop and home X
  G90        ; absolute coordinates
  G0 X-18    ; move above the dock 

  ; attached -> cmd "query_dockable_probe" -> printer.dockable_probe.last_status : 1
  ; docked -> cmd "query_dockable_probe" -> printer.dockable_probe.last_status : 2
  ; unknown : 0

  QUERY_DOCKABLE_PROBE
  
  {% for z in range(300) %} ; while status != 1 (! ATTACHED)
    FORCE_MOVE STEPPER=stepper_z DISTANCE=-1 VELOCITY=10 ; move down 1mm ; TODO : VELOCITY : stepper_z.homing_speed
    _DO_QUERY_DOCKABLE_PROBE
    ;RESPOND MSG="printer.dockable_probe.last_status : "{printer.dockable_probe.last_status}
  {% endfor %}

[gcode_macro _DO_QUERY_DOCKABLE_PROBE]

gcode:
  
  QUERY_DOCKABLE_PROBE
  ;RESPOND MSG="printer.dockable_probe.last_status : "{printer.dockable_probe.last_status}
  
  {% if printer.dockable_probe.last_status == 1 %} ; attached
    { action_raise_error("attached") } ; Abort the current macro (and any calling macros)
    ; BUT ; throws !! Error evaluating 'gcode_macro _DO_QUERY_DOCKABLE_PROBE:gcode': gcode.CommandError: attached
  {% endif %}

The status docked / attached is read outside of the loop, and action_raise_error breaks the loop… but also aborts the homing ! But the probe now being attached, a second “home all” is successfull, and the probe is docked as expected when completed.

It is usable, but uggly. Now considering a delayed_gcode ; not very granular (1 sec if I understand correctly), but the probe jumping from 10 mm, the vertical moves could be as large as 5 or even 7 mm every second… Still have to learn more about delayed gcodes…

Since the macros are rendered from templates at call-time, splitting the check and movement into separate g-codes would allow for successful state checking. I’m thinking something like this pseudo code:

ATTACH:
  // additional setup here if needed
  TOUCH_PROBE

TOUCH_PROBE:
  FORCE_MOVE Z -0.5mm
  CHECK_ATTACH

CHECK_ATTACH:
  { if probe is not attached }
    TOUCH_PROBE
  { end of }

Some sort of sideways recursion. Safety checks might be wise but impossible without additional hardware.

Also note that the EXTRACT macro is only intended to handle the move away from the dock with the probe attached, not the run-up to the dock. Hopefully the docs explain it better, in particular the flow section. If not, let me know how the docs can be improved or what’s lacking.

Thanks for the suggestion, will try this right now !

The system is extremely reliable because it is extremely simplified ; not sure safety checks are needed. But I could later modify the dock, adding some opto (a CNY70 could detect the presence/absence of the probe in the dock… easy to do : a tiny reflective opto, two resistors and a capacitor, and that’s it : been using those for years as tacho probes in my machine tools )

I salted the overrides with RESPOND’s, so I can see what macros are called, and when.
Because of my specific design, MOVE_TO_DOCK_PROBE is now void (does nothing, would make no sense as there’s no known Z coord when attaching)

Currently :

MOVE_TO_INSERT_PROBE just moves the probe above the dock
MOVE_TO_DETACH_PROBE moves the probe down, then wipes it when moving to the right

Should the probe down move be part of MOVE_TO_INSERT_PROBE ? (it works fine as is…) ; also, I will have questions (later) about the xxx_position parameters to be used (for now everything is hard coded).

Nothing was obvious because there’s very few examples on the web, with explanations about the software : only videos showing how to assemble the printed parts and dowloading preexisting code. It is of no interest in this instance.

It’s too soon to make suggestions about the documentation ! And this docking system is probably the only one on the planet (the printer is designed for a Stealthburner, structural parts are machined, and it is designed so the head can move outside of the bed on both sides : I don’t think such a dockable probe and auto z offset could be adapted to a stock CR10 or Ender. (the nozzle has to move from X = -20 to X = +20, relative to bed edges ; and it has a homebrew Stealthburner motorplate).

Maybe some coreXY or H-bot with a flying gantry could benefit from such a system ?

Now time to code !

[EDIT]

Fail…

“!! Macro _TOUCH_PROBE called recursively” error

In this thread "(Macro called Recursively · Issue #1932 · Klipper3d/klipper · GitHub) we can read : “A macro may not invoke itself (either directly or indirectly)”

OTOH, in the macro tutorial , we also read : “A macro is a stored set of commands that can be called from a single gcode command. Macros can call other macros or even themselves”

Last hope before diving into Python is a delayed macro, with timer set to zero when the probe is attached. But I don’t catch the syntax…

[EDIT2]
delayed macro is also a fail.
MOVE_TO_EXTRACT_PROBE doesn’t wait for the timer based procedure to finish. Didn’t think of that ! As a result, Dockable_Probe immediately throws a !! Probe attach failed! error. But the Z move stops as soon as the probe is attached… Still scratching my head…

Only solution I was thinking of was adding a G4 with the maximum duration that could require MOVE_TO_EXTRACT_PROBE (i.e. from Zmax to 0 mm). But G4 is blocking (meaning that delayed macros don’t run in the background)

Dreaming of mutexes…
But testing another idea…

[gcode_macro MOVE_TO_EXTRACT_PROBE] ; default : alias for MOVE_TO_APPROACH_PROBE

rename_existing: BASE_MOVE_TO_EXTRACT_PROBE

gcode:

  RESPOND MSG="MOVE_TO_EXTRACT_PROBE"

  ;{% if printer.dockable_probe.last_status != 1 %} ; if not attached  
    UPDATE_DELAYED_GCODE ID=_TOUCH_PROBE DURATION=0.1
  ;{% endif %}

[delayed_gcode _TOUCH_PROBE] ; start : UPDATE_DELAYED_GCODE ID=beeping DURATION=1
gcode:
  ;RESPOND MSG="moving down one step"
  
  {% if printer.dockable_probe.last_status != 1 %} ; if not attached
    FORCE_MOVE STEPPER=stepper_z DISTANCE=-3 VELOCITY=10 ; move down ; TODO : VELOCITY : stepper_z.homing_speed
    ;M400
    UPDATE_DELAYED_GCODE ID=_TOUCH_PROBE DURATION=0.1
  {% else %}
    UPDATE_DELAYED_GCODE ID=_TOUCH_PROBE DURATION=0 ; STOP
  {% endif %}

  QUERY_DOCKABLE_PROBE ; update last_status for the next iteration

Problem solved !
But had to do what I didn’t want to : modify your plugin code.

    def attach_probe(self, return_pos=None):
        #retry = 0

        self.gcode.run_script_from_command("""
                MOVE_TO_APPROACH_PROBE
                MOVE_TO_DOCK_PROBE
            """)

        while (self.get_probe_state() != PROBE_ATTACHED) :
            self.gcode.run_script_from_command("""STEP_TO_ATTACH_PROBE""")
            #self.toolhead.manual_move([None, None, return_pos[2]], self.travel_speed)

            #retry += 1


        if self.get_probe_state() != PROBE_ATTACHED:
            raise self.printer.command_error('Probe attach failed!')

        if return_pos:
            if not self._check_distance(return_pos, self.approach_distance):
                self.toolhead.manual_move(
                    [return_pos[0], return_pos[1], None],
                    self.travel_speed)

Crude, needing some sanity cheks, but definitely the solution…

Cfg file :

[gcode_macro STEP_TO_ATTACH_PROBE] ; default : alias for MOVE_TO_APPROACH_PROBE

;rename_existing: BASE_STEP_TO_ATTACH_PROBE ; TODO : cmd_*******

gcode:

  RESPOND MSG="STEP_TO_ATTACH_PROBE"

    {% set step = printer.configfile.config.dockable_probe.attach_step|float|abs %} ; mm/s²
    {% set velocity = printer.configfile.config.dockable_probe.attach_speed|float %} ; mm/s
    {% set accel = printer.configfile.config.printer.max_z_accel|float %} ; mm/s²
    
  SAVE_GCODE_STATE NAME=step_state  
  FORCE_MOVE STEPPER=stepper_z DISTANCE=-{step} VELOCITY={velocity} ACCEL={accel}
  RESTORE_GCODE_STATE NAME=step_state

My dockable probe is too different :

  • uses X and Z moves only
  • uses forced moves
  • needs to work without Z homing
  • cannot work with vector moves (would create havoc)

Now the best thing to do is rename it “Bucketable_Probe” for example and remove all unneeded code.

BTW, there’s a problem with the coordinates in your plugin : they are defined as strings, and AFAIK cannot be parsed in a macro. Overridiing macros implies hard coding them… (on my side, I replaced them with x_, y_ and z_ attributes so they can be used in macros.

x_dock_position: -18.0
y_dock_position: 0.0
z_dock_position: 0.0

x_approach_position: -18.0
y_approach_position: 0.0
z_approach_position: 0.0

x_detach_position: 5.0
y_detach_position: 0.0
z_detach_position: 1.0

I also have a question. Line #286, there is :

        if len(self.approach_position) > 2:
            self.toolhead.manual_move([None, None, self.approach_position[2]],
                                      self.travel_speed)

Looks like this moves the head at the same speed, horizontal and vertical ?
Shouldn’t it be attach_speed or detach_speed ?
It is in cmd_MOVE_TO_APPROACH_PROBE

(currently having fun learning Python with your plugin ! Can’t believe it !)

1 Like

Congrats!

BTW, there’s a problem with the coordinates in your plugin : they are defined as strings, and AFAIK cannot be parsed in a macro.

With the templating language (Jinja) you can split the string, like so:

{% set x, y, z = printer.configfile.config.dockable_probe.dock_position.replace(' ', '').split(',') %}
# Those are still strings but if you want floats:
{% set x_coord = x|float %}

The coordinate format is the same as used (virtually) everywhere else in the built-in config blocks.

Looks like this moves the head at the same speed, horizontal and vertical ?
Shouldn’t it be attach_speed or detach_speed ?

This will only do a Z move at travel_speed because attach_speed is for the movement from the approach position to the dock and detach_speed is for the movement from the dock to “scrape off” the probe. Explained slightly differently in the docs.

Have fun with Python!

Thanks for the trick. Ctrl+C/Ctrl+V in OneNote.
Very few answers with Google, but read that it is impossible and requires a call to Python or some inline code !!!
But doing this was the starting point for learning attributes definitions !

As a status update on getting this into “production”, as it were: I’ve submitted a PR!

The process has begun… :slight_smile:

May I suggest to add this, under the “positions” section ? Provides a more explicit error than the cryptic default one…

        # Positions (approach, detach, etc)

        self.approach_position = self._parse_coord(config, 'approach_position')
        self.detach_position   = self._parse_coord(config, 'detach_position')
        self.dock_position     = self._parse_coord(config, 'dock_position')

        if self.detach_position == None :
            raise self.printer.command_error("Option 'detach_position' in section 'dockable_probe' must be specified")
        if self.approach_position == None :
            raise self.printer.command_error("Option 'approach_position' in section 'dockable_probe' must be specified")
        if self.dock_position == None :
            raise self.printer.command_error("Option 'dock_position' in section 'dockable_probe' must be specified")

That was an excellent call-out! Part of the original code was papering over the built-in “missing required option” check/message.

Great ! And much smarter.

BTW, I posted some code (forced moves). This code seems to work. At first glance… In fact it does not. It homes fine, but introduces inconsistancies in the coordinate systems while meshing and only while meshing ! IT IS CRAP ! It is now fixed, after butchering attach_probe(), doing forced moves with calls to get_position(), manual_move() and set_position(). Still learning… But now, it’s really working. No idea why… Spent hours on debugging with no results until I suspected something with the macro. Python did it.

I will stop flooding here, fork your project on Github, and just add a link to my repo, with a short demo video. Will share the designs later, after more testing. (better Stealthburner for Ender’s, dead simple Klicky, and auto Z offset)

BTW, I finally keep the library name and objects names, as it makes life easier when injecting your updates into the modded version I’m using.

Can’t believe how quickly the software problems I had with my designs where solved on klipper.discourse.group ; just mind blowing.

Next project : toolhead Voron logo that blinks everytime a endstop is hit, or a probing is done.

[EDIT] : GitHub - yet-another-average-joe/Dockable_Probe: Forked from clakedcode's Dockable_Probe

[FEATURE REQUEST]

Klipper Probes have two interesting features :

activate_gcode:
deactivate_gcode:

These features are not supported by Dockable_Probe ; it could be usefull, or at least fun : was thinking of giving the user some information about the probe state, by blinking some RGB LED (Voron Stealthburner logo…)

On my side, had a look to the probe and sensor code, and I’m unable to do it. Hopefully it’s just for now… One one side it’s a probe, on the other one a sensor. Couldn’t find the implementation…

BTW, I was able to use Dockable_Probe the way I wanted, using a nozzle probe (KnobProbe). The benefit being that the dock also provides the auto Z offset, while homing and picking the probe. Uses your plugin, without any hack in the code (reverted to genuine Dockable_Probe). As long as the nozzle probe is at a known and fixed position, adding auto Z offset feature to Dockable_Probe is just adding another probe, with a pin and a position, plus basic arithmetics. Will not develop, you know it.

Could be a cool feature !

(will update this post later, with a link showing what can be done)

Theoretically all/most original probe options should still retain their function, see here:

Did you try it with those options and it was unsuccessful? Let me know exactly what went wrong and if I can’t fix it I’ll at least update the docs to not be misleading.