Skip to content
Ch 14 › Part H — Add-on: chamber exhaust fan33 / 36

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 Check

Klipper restarts clean, and Mainsail lists exhaust_fan at target 0 beside chamber_temp, both reading the same temperature.

helperHelper 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 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