# klipper-macros Klipper macros, Klipper modules and scripts for one printer, installed on the printer's Raspberry Pi with `install.sh` and updated through Moonraker. See README.md for the user-facing description. ## The printer Voron 2.4 350, Klipper + Moonraker + Mainsail on a Raspberry Pi 4 (`http://mainsailos.local`, Moonraker API on port 80). AFC Box Turtle with 5 lanes, Blobifier purge bucket, Beacon (Z homing and probing), Nitehawk-SB toolboard (USB), Leviathan mainboard as USB-CAN bridge, bd_pressure (load-check firmware, branch `load-check-extension` in `D:\Claude\bd_pressure-load-check\bd_pressure`), bdwidth (CCD width/motion sensor on USB; switched off: its motion reading stays at 0, its width compensation made the flow jump 77-119% and a USB dropout once shut down Klipper. The macros work with or without `[bdwidth fila_width_0]`; clogs and breaks are caught by AFC's buffer fault detection, `filament_error_sensitivity`), iHeater chamber heater. ## Working with the printer - Read freely through Moonraker: `GET /server/files/config/`, `/server/files/logs/klippy.log`, `/printer/objects/query?...`, `/server/gcode_store`. The merged config Klipper loaded is in klippy.log after `===== Config file =====`. - Do **not** change anything on the printer (upload files, edit config, send G-code, restart services) unless the user asks for that specific action. Prepare files here and let the user upload them. - Never run anything that moves the printer or heats while a print may be running; check `print_stats.state` first. ## Repo layout - `macros/klipper-macros.cfg` is the only file `printer.cfg` includes; it includes everything else. It must be included after `[include AFC/*.cfg]` (a `rename_existing` of an AFC command only works if AFC loaded first). On the Pi, `macros/` is linked as `~/printer_data/config/klipper-macros/` and `scripts/` as `~/printer_data/config/klipper-macros-scripts/`. - `extras/*.py` are linked into `~/klipper/klippy/extras/`. - `scripts/` MCU firmware update scripts (printer-specific IDs). - `tools/effective_config.py` expands the includes like Klipper and compares the result between git refs. ## Rules for macro changes - **Never define the same `[gcode_macro X]` in two files.** Klipper merges sections with the same name and the later file wins completely (this is how heat_soak.cfg's RESUME silently replaced the real RESUME). Include order in `klipper-macros.cfg` matters for this. - `RESUME` and `PAUSE` are defined only in `print/pause_resume.cfg`. AFC renames PAUSE/RESUME at startup and wraps them: `PAUSE -> AFC_PAUSE -> PAUSE -> PAUSE_BASE`, `RESUME -> AFC_RESUME -> RESUME -> HEAT_SOAK_RESUME -> _RESUME_PRINT -> RESUME_BASE`. After a PAUSE, `_RESUME_PRINT` first purges with `BLOBIFIER_CHECKED` (`_RESUME_PURGE`); a failed check raises an error, which aborts the whole chain (AFC_RESUME's restore too), so the printer stays paused. - Only one `[save_variables]`, in `macros/save_variables.cfg`. - `PRINT_END` and `CANCEL_PRINT` share `_PRINT_CLEANUP` (heaters including `iHEATER_OFF`, fan, `M221 S100`, bdwidth off). Anything that must happen at the end of every print goes there. `RESTORE_GCODE_STATE` restores the flow (M221), so reset flow after it, not before. - Do not use `G10`/`G11` in macros: Klipper's `G10` retracts only once until a `G11`, and nothing on this printer sends `G11`. Use `_NOZZLE_RETRACT` (remembered in the saved variable `nozzle_retract`); `PRINT_START` undoes it over the bucket with `_NOZZLE_UNRETRACT`. - Inside `gcode:` use `;` for comments on G-code lines; `#` lines are stripped by the config parser. Never put a comment on an `M117` line: M117 shows the raw rest of the line, comment included. - Errors and warnings go through `_ERROR` / `_WARN` (`macros/messages.cfg`), not `action_raise_error` / `RESPOND TYPE=error`, so they also show on the display. `action_raise_error` aborts while rendering, so an `M117` in the same macro would never run. No double quotes in `MSG`/`DISPLAY`. - Nozzle guard (`print/nozzle_guard.cfg`, `extras/bdp_guard.py`): wrap only moves where nothing may touch the nozzle in `_GUARD_ON PHASE=x` / `_GUARD_OFF`. Never leave it on across contact homing, extrusion, purges, the Blobifier park or AFC cuts and loads: in `mode: stop` that is an instant shutdown. - After a refactor run `python tools/effective_config.py HEAD` and check that only the intended sections changed. - Render-test macros with Jinja2 using Klipper's delimiters (`jinja2.Environment('{%', '%}', '{', '}')`) and mocked `printer` objects. ## Rules for the Python modules (`extras/`) - Klipper only loads changed `.py` files when the **Klipper service** restarts (`sudo systemctl restart klipper`); `RESTART` and `FIRMWARE_RESTART` keep the old code in memory. - Event handlers must never raise: an exception in a handler shuts Klipper down. Example: Beacon sends `homing:home_rails_end` with `rails=[]` and a homing state whose `get_trigger_position` raises. - Keep them read-only wrappers where possible (look up the other module's object, do not patch third-party files). ## Reference values - Purge check (150 mm Blobifier purge): good load = longest pressure 13-18 s, peak 64-181; empty nozzle about 30 ms. A real failed load in a print start (AFC reported the lane loaded, nothing reached the nozzle): longest 1915 ms, peak 45, caught by the 5000 ms limit. - Nozzle guard (bdp_guard): peaks 1-8 in home_xy, QGL, Z homing, mesh and the Z check; the brush scrub 47 (4 reads above 30 in a row, so it runs in log mode); a light finger push on the cold nozzle gives 41 (threshold 30). - Z check after a contact homing: Beacon vs Klipper Z about 0.06 mm. - XY drift check: endstop noise about 0.01-0.02 mm; limit 0.1 mm (one motor skipping one cycle = 0.4 mm shows as 0.2 mm in X and Y). ## Git - LF line endings everywhere (`.gitattributes`); the files run on Linux. - Commit only when the user says so; never push without asking. Remote: `https://mangavalk.com/mangavalk/klipper-macros.git`.