Hi all,
I wanted to rescue long prints after a power cut or a frozen Pi without writing the position to the SD card every layer, which is how most power-loss macros work. I ended up with an add-on that measures where the nozzle stopped instead, then finds that exact spot in the G-code. It’s been working on my Ender 3 Pro, so I’m sharing it for feedback.
Repo: GitHub - mikeoverbay/klipper-power-loss-recovery · GitHub
The ideas
1. The machine is the record. With the power off, lead screws and belts don’t move, so the nozzle is still where it stopped. Klipper keeps each stepper’s MCU step count continuous through set_position() and homing (get_mcu_position()), so:
stop position = position after homing + (steps before homing − steps after) × step distance
That works for X, Y and Z: lift 5 mm, home X/Y, home Z at the X/Y corner, read the counts.
2. Heat before anything moves. After a power cut the nozzle has cooled into the top layer, so a cold lift tears the part off the bed. The command heats the nozzle in place (no motion) to 170 °C first. If it can’t get there, it turns the heater off and aborts without moving.
3. The layer comes from the log, not just from Z. klippy.log already records the print file position (sd_pos) about once a second. That gives the layer directly. It matters because with a large babystep, the measured Z alone can point at the wrong layer.
4. The exact move comes from X/Y. Inside that layer, it picks the extruding move whose path (the segment, not just its end points) passes closest to the measured X/Y. One surprise: the logged sd_pos was a couple of seconds behind the nozzle, not ahead of it. The last log lines never reach the SD card when the host dies, and the MCU keeps running its buffered moves. So the search runs forward from the logged position.
5. The babystep comes back too. measured Z − layer Z = the SET_GCODE_OFFSET that was active. The resume file re-applies it and clears it at the end.
6. One button, one popup. In Mainsail it’s a single POWER LOSS RESUME macro. It heats, measures and analyses, then a Mainsail action:prompt dialog shows:
- the file
- the layer
- the resume point and how far it is from the measured spot
- what it was printing (from Cura’s
;TYPE:labels) - the offset
Start print then builds the resume file into a hidden gcodes/.power_loss/ folder (so it can’t be deleted by mistake) and starts it. The idle timeout is held off between measuring and printing so homing isn’t lost.
Results on my machine
Ender 3 Pro, SKR Mini E3 V2, optical Z endstop, Klipper v0.13, Cura.
Z measurement. Set a known Z, FIRMWARE_RESTART to forget it, measure:
| Z before reset | Measured | Error |
|---|---|---|
| 3.000 | 3.000 | 0.000 mm |
| 12.345 | 12.342 | −0.003 mm |
| 30.000 | 30.003 | +0.003 mm |
| 57.800 | 57.800 | 0.000 mm |
| 100.000 | 100.005 | +0.005 mm |
That’s within ±2 microsteps of 0.0025 mm.
Exact resume. Two real mid-print stops, both in infill. The chosen resume move was 0.06 mm and 0.07 mm from the measured X/Y.
Limitations
- Needs a Z-min endstop the gantry triggers (switch or optical), with the X/Y home corner clear of the part. A bed probe homing at the bed centre via
safe_z_homewould drive into the part. - Axes must hold position unpowered, and nobody should push the head before recovering.
- X/Y motors can snap up to ±2 full steps when they’re enabled. Z is the precise axis; X/Y have been within a few hundredths of a millimetre so far.
- Cura G-code only for now (
;LAYER:markers, Z set before them). NoG2/G3arcs yet. - The part still has to stay stuck while cold, and the join usually shows.
- It only reads
stepper_z, so multi-Z setups assume the Z motors stay in sync.
Questions for the experienced folks
- Is relying on
MCU_stepper.get_mcu_position()staying continuous acrossset_position()and homing reasonable, or likely to break in future Klipper versions? - Is there a better way to get the last executed position than the measurement plus the (lagging)
sd_posin the log? - Edge cases I’ve missed?
Thanks for reading. Feedback welcome, and the README in the repo has the details and install steps.