Rack fan control — linear proportional curve vs PID? Looking for advice on optimal logic

I’m controlling a PWM fan (Noctua) inside a small homelab rack with an ESP32-C6 (ESPHome). The fan speed is set based on a temperature reading. I’d love feedback on whether my control logic is optimal or if I should switch approach (e.g. PID).

Current setup

  • Temperature sensor: Sonoff SNZB-02P (Zigbee, via ZHA)
  • Fan: Noctua PWM, driven by an ESP32-C6 via a number entity (fan_target_percent, 0-100%)
  • Control logic: a Home Assistant automation with a linear proportional curve: 31°C → 0%, 36°C → 100%, linear in between
  • The automation triggers on temperature change and every 5 minutes as a safety net
  • It only writes the new value if it differs from the current one (to avoid unnecessary writes)

A quirk with the Zigbee sensor

The SNZB-02P only reports every 30-60 minutes when the temperature is stable (it reports on change / max interval to save battery). When the temperature moves, it reports more often. That’s why I added the 5-minute time_pattern trigger as a fallback.

My questions

  1. For this kind of slow-changing thermal system, is a linear proportional curve good enough, or would a PID controller give meaningfully better results (more stable temperature, less fan hunting)?
  2. Given the Zigbee sensor’s infrequent reporting when stable, does it even make sense to use PID (which usually wants regular sampling)? Would a faster local sensor on the ESP (e.g. DS18B20) be a prerequisite for PID to work well?
  3. Should the control logic live in Home Assistant (as now) or be moved entirely into the ESP (ESPHome climate/PID + local sensor) for autonomy and faster response?
  4. Any tips on tuning the curve itself — e.g. is a linear ramp the best shape, or would a different curve (steeper near the top) make more sense?

Happy to share my full ESPHome config and the automation YAML if useful. Thanks in advance for any insight!

Does it keep your racked equipment cool?

If the answer is yes then that too is the answer to your question.