Exact Power Loss Recovery for Klipper
Klipper PLR records the toolhead position, extruder position, G-code file path and the exact virtual_sdcard.file_position at the moment a configured power-loss input changes state. After restart it builds a recovery G-code file that returns the machine to the saved physical height and continues from the saved byte position.
Important: Power-loss recovery is hardware-dependent. A backup supply/supercapacitor must keep the host and MCU alive long enough for the capture macro to execute. Test thoroughly before relying on it for valuable prints.
- Exact file-position recovery rather than replaying a complete layer.
- Saves X/Y/Z/E, file path and file offset in Klipper
save_variables. - Restores part-cooling fan state and extrusion mode (absolute or relative).
- Keeps the slicer thumbnail, so the recovery file has a preview in Mainsail.
- Survives a second power loss during a recovery print.
- Layer-height checkpoint as a secondary recovery aid.
- Automatic recovery-file generation after restart.
- Automatic cleanup after a recovery print reaches
END_PRINT. - Idempotent installer with backups; unattended re-runs for Moonraker updates.
- Handles file names containing spaces, apostrophes and quotes.
- Works with G-code stored in the normal
printer_data/gcodesdirectory, including files copied from USB storage.
- Klipper with
virtual_sdcard.file_positionsupport. - Moonraker plus Mainsail or Fluidd.
- A free MCU GPIO connected to a power-loss detection circuit.
- Backup power that keeps the host and MCU alive during the detection/capture window.
- Absolute extrusion (
M82).
This is required, and is not bundled here because KIAUH is GPL-3.0 licensed while this repository is MIT.
The installer handles it for you, in this order:
- If it is already in
~/klipper/klippy/extras/, nothing happens. - If you have KIAUH cloned, the local copy is used. No network needed.
- Otherwise it is downloaded from the KIAUH repository.
If the download fails or the KIAUH layout has changed, install it yourself
and re-run install.sh:
# through KIAUH: Advanced -> G-Code Shell Command
# or, if KIAUH is already cloned:
cp ~/kiauh/kiauh/extensions/gcode_shell_cmd/assets/gcode_shell_command.py \
~/klipper/klippy/extras/Note that updating Klipper can remove this extension. If PLR suddenly stops
working after a Klipper update with Unknown config object 'tgcode_shell_command', reinstall it the same way.
git clone https://github.com/Novin3dp/klipper-plr.git
cd klipper-plr
./install.shIf the cloned files do not have executable metadata on your system, use:
chmod +x install.sh uninstall.sh
./install.shIf Klipper PLR was already installed from this repository, do not clone it again. Update the existing checkout and run the installer so the new files are copied into printer_data and any required configuration changes are applied:
cd ~/klipper-plr && git pull && ./install.shThe installer keeps the previously saved printer-specific settings (such as the power-loss GPIO and Z lift) in ~/printer_data/plr/install.conf, so an existing installation normally does not require entering them again.
The installer automatically detects the current user and standard Klipper/Moonraker paths. On a fresh installation it asks only for:
- Power-loss GPIO, for example
PB2. - Z lift, measured on the actual printer.
The installer also asks for optional default bed/nozzle temperatures. Existing settings are stored locally in printer_data/plr/install.conf, so future Moonraker updates can reuse them.
Do not guess this value. A wrong value can cause a nozzle collision.
- Remove the print from the bed.
- Home all axes and bring the nozzle to the bed.
- Raise Z exactly 10 mm.
- Restart Klipper so the MCU position is forgotten.
- Run
G28 X0 Y0. - Measure the actual nozzle-to-bed distance.
Z_LIFT = measured distance - 10.
If you cannot safely measure this value, abort the installer and measure it first.
The installer cannot edit a slicer profile reliably, so these two settings remain manual:
Either mode works. The build script reads the last M82 / M83 before the cut
point and rebuilds the header to match: absolute restores the saved E position,
relative emits G92 E0 and M83, because with relative deltas the absolute
position is irrelevant.
If the slicer emits neither, absolute (M82) is assumed.
In Before layer change G-code, add the equivalent of:
;BEFORE_LAYER_CHANGE
;[layer_z]
PLR_SAVE_LAYER Z=[layer_z]The exact placeholder syntax depends on the slicer. OrcaSlicer/PrusaSlicer profiles commonly expose the layer height as [layer_z].
After installation:
RESTART
QUERY_BUTTON BUTTON=power_loss
PLR_STATUS
With normal printer power present, the input should report RELEASED. If the electrical logic is inverted, change the ! polarity in ~/printer_data/config/plr.cfg and restart Klipper.
Before a real print, follow TESTING.md and perform the dry run with the bed clear and no filament.
Cutting power to a running Linux host can corrupt the SD card. The installer can enable an automatic halt right after the capture.
The sequence is:
24V lost -> capture writes variables.cfg -> wait N seconds -> sync -> halt
The shutdown worker is detached with setsid, because Klipper shuts itself
down within moments of the power loss and would otherwise take the script
with it.
Only enable this if your backup power holds the host up long enough. A halt needs roughly 10-15 seconds on top of the capture. If the supercapacitor drains first you get exactly the uncontrolled power cut you were trying to avoid, so nothing is gained. Measure how long your host actually survives before enabling it.
Enabling this installs /etc/sudoers.d/klipper-plr, allowing the Klipper
user to run /sbin/shutdown without a password. The rule is validated with
visudo -c before installation, so a malformed rule cannot lock you out of
sudo. uninstall.sh removes it.
One behaviour to be aware of: on a brief brownout the host halts and stays halted, even if mains power returns a second later. It only comes back when its own supply is cycled. If the host is powered from the same 24V rail this resolves itself; with an independent supply you have to power it manually.
To turn it off later, comment out the RUN_SHELL_COMMAND CMD=PLR_SHUTDOWN
line in ~/printer_data/config/plr.cfg and restart.
During a print, the configured power-loss input triggers PLR_CAPTURE_POSITION. The macro stores:
- X/Y/Z/E
- G-code file path
- exact file offset
- a recovery-pending flag
After the printer is powered back on and Klipper is restarted, a delayed macro builds a file under:
~/printer_data/gcodes/plr/
The build script then reports the result itself and prints how to start it:
PLR: recovery file ready (18432 G-code lines)
plr/<file>.gcode
Start it with: PLR_RESUME
or: SDCARD_PRINT_FILE FILENAME='plr/<file>.gcode'
PLR_RESUME is the safer of the two: it quotes the name for you, which matters
when the file name contains a space or an apostrophe.
If the build fails, it says so and the previous recovery file is left untouched — it never reports success it cannot back up.
After a successful recovery print, the END_PRINT patch schedules automatic cleanup.
| Command | Purpose |
|---|---|
PLR_RESUME |
Start the recovery print that was built |
PLR_STATUS |
Display saved recovery information |
PLR_CAPTURE_POSITION |
Manually capture the current position for testing |
PLR_SAVE_LAYER Z=... |
Store a layer-height checkpoint |
G31 |
Clear PLR data when no recovery is pending |
PLR_FORCE_CLEAR |
Force-clear PLR data |
The resume header rebuilds the machine state that lived before the cut point and is therefore missing from the resumed body:
| State | How it is recovered |
|---|---|
| Bed and nozzle temperature | parsed from START_PRINT |
| Position X/Y/Z | from the capture |
| Extruder position | G92 E from the capture (absolute mode) |
| Extrusion mode | last M82/M83 before the cut point |
| Part cooling fan | last M106/M107 before the cut point |
| Preview thumbnail | slicer header copied from the original file |
Fan recovery skips secondary fans (M106 P1, P2, ...) and only restores
the default part fan. If the slicer had the fan off at that point, M107 is
emitted rather than nothing.
Not currently restored: M220 speed factor, M221 flow factor, and bed mesh.
Acceleration and velocity limits are not restored either, but Orca-style
output re-emits SET_VELOCITY_LIMIT on almost every feature change, so they
correct themselves within a few moves.
Power loss during a recovery print. The captured path is then the recovery
file itself, which is also where the script writes. Rebuilding in place would
delete the source mid-read and leave a header with no moves — and no
END_PRINT, so the heaters would stay on. The script detects this, snapshots
the source first, and rebuilds from the snapshot. Layer comments are kept in the
body for exactly this reason, so a chained recovery still reads the real layer
height instead of falling back to the captured Z.
Power loss while idle. The detection input fires whenever 24V drops, including when you flip the mains switch on a machine that is not printing. The capture is then skipped entirely — storing a placeholder path would raise a false recovery on the next boot and, worse, overwrite a genuine pending one. The safe shutdown still runs. The console shows:
PLR power loss while idle - nothing to recover
PLR shutting down host
File names with spaces or apostrophes. Klipper parses extended parameters
with shlex and then ast.literal_eval, so a path has to survive both layers.
An unescaped name containing an apostrophe aborts the capture macro part-way —
which means the recovery flag is never written either, and PLR silently does
nothing. Both the capture macro and the reader handle every quoting case.
A slicer that emits no ;Z: comments. The script falls back to the captured
Z and says so loudly, because that value may be a z-hop height rather than the
real layer height. Check the G1 Z line in the resume file before starting it.
An empty or truncated body. A read error through a pipe still reports success, so the emitted body is counted rather than trusted. If nothing came out, the build fails and the existing recovery file is left alone.
- Never assume a recovery is safe until the dry run has been completed on the actual printer.
- Do not touch or manually move the machine after power loss unless your recovery procedure explicitly accounts for it.
- Recovery near the Z travel limit is refused instead of silently clamping the height.
- The resume builder deliberately moves X/Y before lowering Z to the saved layer height.
- Bed mesh is not automatically restored by the generated recovery header; add the appropriate mesh-load command if your machine requires it.
- If a recovery is cancelled midway, PLR data is intentionally retained so it can be investigated or retried.
cd ~/klipper-plr
./uninstall.shThe uninstall script removes the PLR include/configuration and generated recovery files, but intentionally leaves gcode_shell_command.py and the END_PRINT snippet untouched.
This project is being developed and tested on real Klipper hardware. Compatibility depends on the printer's kinematics, homing behavior, slicer G-code and power-loss hardware.
MIT. See LICENSE.