##################################################################### # 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 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) %} {% 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 %} # Separate macros so the status is read when they run, not when # BLOBIFIER_CHECKED is rendered [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 %}