Step 14.33 — Add the exhaust fan to printer.cfg¶
Step 33 of 36 with a helper · Chapter 14 — Calibration and tuning
Parts: none.
Do: Paste the block below under [fan_generic nevermore]. Add the first line under SET_GCODE_OFFSET Z=0 in PRINT_START, the second under TURN_OFF_HEATERS in PRINT_END. SAVE & RESTART.
#####################################################################
# Chamber exhaust fan — ADDED at Ch 14 Part H (add-on)
# Stock Voron 2.4r2 exhaust housing with a 6020 24 V fan on the
# Leviathan's FAN0. The chamber thermistor drives it through a
# combined sensor, so [temperature_sensor chamber_temp], its gcode_id
# and every macro that reads it stay exactly as Ch 12 wrote them.
#####################################################################
[temperature_fan exhaust_fan]
pin: PB7 # Leviathan FAN0, not the toolboard's FAN0
max_power: 1.0
shutdown_speed: 0.0 # a fault leaves it off, as LDO's stock exhaust did
kick_start_time: 0.5
sensor_type: temperature_combined
sensor_list: temperature_sensor chamber_temp
combination_method: max
maximum_deviation: 999.9 # one sensor: nothing to compare
min_temp: 0 # the same limits as chamber_temp
max_temp: 100
control: watermark
max_delta: 2.0 # on at ceiling + 2, off at ceiling - 2
target_temp: 0 # 0 = off until a print sets a ceiling
min_speed: 0.0
max_speed: 1.0
[gcode_macro _EXHAUST]
description: Set the exhaust ceiling for this print. PRINT_START calls it. FILTER=1 (a Nevermore material) is never vented.
variable_hot_above: 45 # a ceiling above this is a hot-chamber material: stay off
variable_purge_min: 10 # PRINT_END: minutes flat out after a vented print
gcode:
{% set chamber = params.CHAMBER|default(0)|float %}
{% set target = params.TARGET|default(0)|float %}
{% set filter = params.FILTER|default(0)|int %}
{% if chamber > 0 or target > hot_above or filter %}
{% set target = 0 %}
{% endif %}
UPDATE_DELAYED_GCODE ID=_EXHAUST_OFF DURATION=0
SET_TEMPERATURE_FAN_TARGET TEMPERATURE_FAN=exhaust_fan TARGET={target}
{action_respond_info("Exhaust: off" if target == 0 else "Exhaust: ceiling %.0f C" % target)}
[gcode_macro _EXHAUST_PURGE]
description: PRINT_END calls it. After a vented print, flat out for purge_min, then off.
gcode:
{% set e = printer["gcode_macro _EXHAUST"] %}
{% if printer["temperature_fan exhaust_fan"].target > 0 and e.purge_min > 0 %}
SET_TEMPERATURE_FAN_TARGET TEMPERATURE_FAN=exhaust_fan TARGET=1 ; below any chamber: flat out
UPDATE_DELAYED_GCODE ID=_EXHAUST_OFF DURATION={e.purge_min * 60}
{% else %}
SET_TEMPERATURE_FAN_TARGET TEMPERATURE_FAN=exhaust_fan TARGET=0
{% endif %}
[delayed_gcode _EXHAUST_OFF]
gcode:
SET_TEMPERATURE_FAN_TARGET TEMPERATURE_FAN=exhaust_fan TARGET=0
_EXHAUST CHAMBER={chamber} TARGET={params.EXHAUST|default(0)|float} FILTER={1 if filter else 0}
_EXHAUST_PURGE
Check
Klipper restarts clean, and Mainsail lists exhaust_fan at target 0 beside chamber_temp, both reading the same temperature.
Helper Reads the pin line back off the screen: PB7, not PF7.
What you're looking at
A [temperature_fan] is a fan Klipper switches against a temperature target, used here as a ceiling. It reads the existing chamber sensor through a combined sensor, so nothing Ch 12 wrote changes. Two small macros set and clear the ceiling for each print.
Caution
Leave [temperature_sensor chamber_temp] exactly as it is. The combined sensor reads it, so its pin is declared once, and PRINT_START's chamber wait and M105's chamber_th keep working.
Tip
SET_TEMPERATURE_FAN_TARGET TEMPERATURE_FAN=exhaust_fan MAX_SPEED=0.6 quietens the fan until the next restart.
Source: Klipper docs § temperature_fan · Klipper docs § Combined temperature sensor · Klipper temperature_fan.py · Voron docs — Chamber Temperature & Exhaust Fan · review/2026-10-03-exhaust/test_macros.py