PID Controlled Exhaust Fan (Quiet!)

OMG

in my pain and desperation i turned to chatGPT and after arguing and denying all the wrong solutions i now have a workaround
if i put SET_FAN_SPEED FAN=fan2 SPEED=0 right before the TEMPERATURE_WAIT it works
let’s ignore the fact that it is such an obvious approach that i could have tried it even without understanding why ><
It seems to be a problem with the klipper resend loop during the wait command which somehow sends the wrong last fan2 state ?

here’s the long and as always way to chatty version of it

The critical mechanism: fan state replay during G-code blocking

This is not documented, and it’s subtle.

What Klipper does internally

When a G-code command blocks (and TEMPERATURE_WAIT is one of the very few that does):

  1. Klipper continues to run the reactor loop

  2. It periodically replays last-known fan states to the MCU

  3. This is done to keep MCU state synchronized while host G-code execution is paused

Normally, this is harmless.

However…


Why this hits fan2 only

Klipper tracks fan state in two layers:

  • Logical state (host-side)

  • MCU pin state (low-level)

If a fan was ever written with a non-zero value in the current session — even briefly — that value becomes the “last-known” state.

During a blocking wait:

  • Klipper re-sends that value

  • Even if the logical state is currently zero

  • No G-code is emitted

  • No macro is involved

This is why:

  • You cannot find it in config

  • You cannot reproduce it outside TEMPERATURE_WAIT

  • Emergency stop behaves differently (host disconnect → MCU reset → pin cleared)


The smoking gun: fan2 is written once earlier in the script

Not necessarily visibly.

Typical hidden writes:

  • M106 without P (defaults to fan0 but still updates fan table)

  • SET_FAN_SPEED FAN=fan2 SPEED=0 (yes, zero still registers)

  • A macro that touches all fans

  • A conditional that evaluates differently during normal execution

Once Klipper has ever touched fan2, it is eligible for replay.


How to prove this beyond doubt

Add one line before your TEMPERATURE_WAIT:

SET_FAN_SPEED FAN=fan2 SPEED=0

Then restart Klipper and re-run your script.

If the fan stops turning on, this confirms fan-state replay.

Why?

  • You overwrite the “last-known” cached state with zero

  • The replay sends zero instead of the stale value