Files

221 lines
11 KiB
INI
Raw Permalink Normal View History

#####################################################################
# Purge check - did the filament really reach the nozzle during the
# Blobifier purge after a tool change?
#
# Two independent checks around the purge AFC runs after every load:
# 1. bdwidth motion: extruded mm vs mm the sensor saw moving past it.
# Catches a grinding extruder, a tangle or a snapped filament.
# 2. bd_pressure (needs the load-check firmware extension): how long
# the nozzle pressure stayed above the threshold. Catches filament
# that never reached the melt zone. Skipped automatically on stock
# firmware or when bdpressure_check.py is not installed.
#
# Needs: bdwidth_status.py in ~/klipper/klippy/extras/
# bdpressure_check.py in ~/klipper/klippy/extras/ (optional)
# (klipper/install.sh links both; guide: klipper/load_check/afc_blobifier/README.md)
# Hook: in [AFC] (AFC/AFC.cfg) change poop_cmd: BLOBIFIER
# to poop_cmd: BLOBIFIER_CHECKED
#
# Rollout:
# Phase 1 mode "report": only prints the results after every purge.
# Collect a few days of values, then set min_ratio and
# pressure_min_ms well below the lowest good ones.
# Phase 2 mode "enforce": pauses when a check fails.
#
# Costs about 2 x (sample_time + 0.5 s) per swap, 5 s at sample_time 2,
# so the sensors are quiet and up to date before and after the purge.
# When bdwidth is not reading, add bdwidth_warmup_ms: it is switched on for
# the check (motion only, SET_BDWIDTH ENABLE_MOTION) with its runout pause
# off meanwhile (SET_FILAMENT_SENSOR), and outside a print off again after.
#####################################################################
[bdwidth_status]
sensor: fila_width_0
# Remove this section if bdpressure_check.py is not installed. With it
# installed, a bd_pressure that does not answer (stock firmware, hung or
# unplugged sensor) fails the check, so in enforce mode every purge pauses;
# PURGE_CHECK_IGNORE_SENSOR lets the current print finish without it.
[bdpressure_check]
sensor: bd_pa
[gcode_macro _PURGE_CHECK_CFG]
variable_mode: "enforce" # report | enforce | off
variable_motion_check: False # False: skip the bdwidth motion check, pressure only
variable_min_ratio: 0.4 # placeholder: sensed mm / extruded mm below this = not feeding
variable_min_extrude: 20 # skip the checks for purges shorter than this (mm)
variable_pressure_min_ms: 5000 # placeholder: longest continuous pressure needed (ms)
# bdwidth is not reading after SET_BDWIDTH ... COMMAND=DISABLE and after
# every Klipper restart until an ENABLE (outside a print, often early in a
# print start). The check then switches it on and waits this long for the
# first readings (its first read often fails and it retries after 10 s).
variable_bdwidth_warmup_ms: 15000
variable_start_counts: 0
variable_start_e: 0.0
variable_last_failed: False # last check failed in enforce mode (read by _RESUME_PURGE_VERIFY)
variable_ignore_missing_sensor: False # set by PURGE_CHECK_IGNORE_SENSOR, cleared at print end
# Hotter purge: the leftover of the previous filament (the piece below the
# cutter) has to melt to be pushed out. When the filament that last went
# through the nozzle (saved variable nozzle_last_temp) needs more than the
# new one plus flush_margin, the purge runs at that temperature and the
# nozzle cools back to the new filament's afterwards (seen: ASA leftover,
# TPU loaded at 210C, purge only worked after heating by hand).
variable_flush_margin: 10 # C; smaller differences purge at the new temperature
variable_flush_restore: 0 # runtime: temperature to cool back to after a hot purge
gcode:
[gcode_macro BLOBIFIER_CHECKED]
description: Blobifier purge with bdwidth motion and bd_pressure checks (use as poop_cmd in [AFC])
gcode:
{% set cfg = printer["gcode_macro _PURGE_CHECK_CFG"] %}
{% set wait = (printer.bdwidth_status.sample_time * 1000 + 500)|int %}
{% set pressure = 'bdpressure_check' in printer %}
{% set bdw = printer.bdwidth_status.name %}
# If bdwidth is not reading (outside a print, early in a print start,
# after a Klipper restart) it is switched on for the check, with its
# runout response off meanwhile, so an empty lane does not make bdwidth
# pause the printer (the check reports it instead). Afterwards the
# runout response is restored; bdwidth itself is only switched off
# again outside a print, so a print never loses it.
{% set has_bdw = printer.bdwidth_status.present|default(true) %}
{% set bdw_on = has_bdw and cfg.motion_check and not printer.bdwidth_status.reading|default(false) %}
{% set bdw_off = bdw_on and printer.print_stats.state not in ["printing", "paused"] %}
{% set runout_was = 1 if printer.bdwidth_status.enabled else 0 %}
# Without the motion check, bdwidth's own runout response is off during
# the purge too, so a bdwidth that does not see the movement cannot
# pause the printer here; the pressure check judges the purge.
{% set runout_off = has_bdw and (bdw_on or not cfg.motion_check) %}
_PURGE_FLUSH_HEAT
{% if cfg.mode == "off" %}
BLOBIFIER {rawparams}
{% else %}
M400
{% if runout_off %}
SET_FILAMENT_SENSOR SENSOR={bdw} ENABLE=0
{% endif %}
{% if bdw_on %}
SET_BDWIDTH NAME={bdw} COMMAND=ENABLE_MOTION
G4 P{cfg.bdwidth_warmup_ms}
{% endif %}
G4 P{wait}
_PURGE_CHECK_START
{% if pressure %}
BDP_CHECK_START
{% endif %}
BLOBIFIER {rawparams}
M400
{% if pressure %}
BDP_CHECK_STOP
{% endif %}
G4 P{wait}
_PURGE_CHECK_END
{% if bdw_off %}
SET_BDWIDTH NAME={bdw} COMMAND=DISABLE
{% endif %}
{% if runout_off %}
SET_FILAMENT_SENSOR SENSOR={bdw} ENABLE={runout_was}
{% endif %}
{% endif %}
_PURGE_FLUSH_DONE
# Separate macros so the status is read when they run, not when
# BLOBIFIER_CHECKED is rendered
[gcode_macro _PURGE_FLUSH_HEAT]
description: Helper for BLOBIFIER_CHECKED: heat to the previous filament's temperature when it is hotter than the new one
gcode:
{% set cfg = printer["gcode_macro _PURGE_CHECK_CFG"] %}
{% set old = printer.save_variables.variables.nozzle_last_temp|default(0)|int %}
{% set new = printer.extruder.target|int %}
{% set top = printer.configfile.settings.extruder.max_temp|int - 15 %}
{% set hot = [old, top]|min %}
{% if new > 0 and hot > new + cfg.flush_margin %}
RESPOND MSG="Purge at {hot}C: the previous filament needs {old}C, back to {new}C after the purge"
SET_GCODE_VARIABLE MACRO=_PURGE_CHECK_CFG VARIABLE=flush_restore VALUE={new}
M104 S{hot}
TEMPERATURE_WAIT SENSOR=extruder MINIMUM={hot - 2}
{% else %}
SET_GCODE_VARIABLE MACRO=_PURGE_CHECK_CFG VARIABLE=flush_restore VALUE=0
{% endif %}
[gcode_macro _PURGE_FLUSH_DONE]
description: Helper for BLOBIFIER_CHECKED: cool back after a hot purge and remember the new filament's temperature
gcode:
{% set cfg = printer["gcode_macro _PURGE_CHECK_CFG"] %}
{% set new = cfg.flush_restore|int if cfg.flush_restore|int > 0 else printer.extruder.target|int %}
{% if cfg.flush_restore|int > 0 %}
M104 S{new}
TEMPERATURE_WAIT SENSOR=extruder MAXIMUM={new + 3}
SET_GCODE_VARIABLE MACRO=_PURGE_CHECK_CFG VARIABLE=flush_restore VALUE=0
{% endif %}
{% if new > 0 %}
SAVE_VARIABLE VARIABLE=nozzle_last_temp VALUE={new}
{% endif %}
[gcode_macro _PURGE_CHECK_START]
gcode:
SET_GCODE_VARIABLE MACRO=_PURGE_CHECK_CFG VARIABLE=start_counts VALUE={printer.bdwidth_status.total_move}
SET_GCODE_VARIABLE MACRO=_PURGE_CHECK_CFG VARIABLE=start_e VALUE={printer.toolhead.position.e}
[gcode_macro _PURGE_CHECK_END]
gcode:
{% set cfg = printer["gcode_macro _PURGE_CHECK_CFG"] %}
{% set s = printer.bdwidth_status %}
{% set lane = printer.AFC.current_load|default("?", true) %}
{% set extruded = printer.toolhead.position.e - cfg.start_e %}
{% set failed = [] %}
{% if extruded < cfg.min_extrude %}
RESPOND MSG="PURGE_CHECK {lane}: only {'%.1f' % extruded} mm extruded, skipped"
{% else %}
{% set motion_on = cfg.motion_check and s.active != "disable" %}
{% if not cfg.motion_check %}
{% set motion = "motion check off" %}
{% elif not motion_on %}
{% set motion = "motion skipped (bdwidth disabled)" %}
{% else %}
{% set sensed = (s.total_move - cfg.start_counts)|abs / s.linear_motion %}
{% set ratio = sensed / extruded %}
{% set motion = "motion %.1f/%.1f mm ratio %.2f, width %.2f" % (sensed, extruded, ratio, s.diameter) %}
{% if ratio < cfg.min_ratio %}
{% if s.total_move == 0 and s.diameter == 0 %}
{% set _ = failed.append("no data from bdwidth since Klipper start (sensor connected?)") %}
{% else %}
{% set _ = failed.append("filament not moving past bdwidth") %}
{% endif %}
{% endif %}
{% endif %}
{% if 'bdpressure_check' in printer and printer.bdpressure_check.available %}
{% set p = printer.bdpressure_check %}
{% set press = "pressure longest %d ms, total %d ms, peak %d" % (p.longest_ms, p.above_ms, p.peak) %}
{% if p.longest_ms < cfg.pressure_min_ms %}
{% set _ = failed.append("no sustained nozzle pressure") %}
{% endif %}
{% elif 'bdpressure_check' in printer %}
{% if cfg.ignore_missing_sensor %}
{% set press = "pressure skipped: bd_pressure not answering (ignored, PURGE_CHECK_IGNORE_SENSOR)" %}
{% else %}
{% set press = "bd_pressure not answering" %}
{% set _ = failed.append("bd_pressure not answering (power-cycle the sensor, or PURGE_CHECK_IGNORE_SENSOR to finish this print without the check)") %}
{% endif %}
{% else %}
{% set press = "pressure skipped (no bdpressure_check)" %}
{% endif %}
{% set checked = motion_on or 'bdpressure_check' in printer and printer.bdpressure_check.available %}
{% set verdict = 'FAIL: ' ~ failed|join(', ') if failed else ('OK' if checked else 'NOTHING CHECKED') %}
RESPOND MSG="PURGE_CHECK {lane}: {motion}; {press} -> {verdict}"
{% if failed and cfg.mode == "enforce" %}
_WARN MSG="PURGE_CHECK: {lane} {failed|join(', ')} - check extruder and lane" DISPLAY="Purge check failed: {lane}, paused"
SET_GCODE_VARIABLE MACRO=_PURGE_CHECK_CFG VARIABLE=last_failed VALUE=True
PAUSE ; no effect while already paused (resume purge): see _RESUME_PURGE_VERIFY
{% endif %}
{% endif %}
[gcode_macro PURGE_CHECK_IGNORE_SENSOR]
description: Let purges pass without bd_pressure until the print ends (ENABLE=0 to undo)
gcode:
{% if params.ENABLE|default(1)|int %}
SET_GCODE_VARIABLE MACRO=_PURGE_CHECK_CFG VARIABLE=ignore_missing_sensor VALUE=True
RESPOND MSG="PURGE_CHECK: an unavailable bd_pressure is ignored until the print ends (purges are not checked for pressure then)"
{% else %}
SET_GCODE_VARIABLE MACRO=_PURGE_CHECK_CFG VARIABLE=ignore_missing_sensor VALUE=False
RESPOND MSG="PURGE_CHECK: an unavailable bd_pressure fails the check again"
{% endif %}