Zum Inhalt springen

Temperature PID

Dieser Inhalt ist noch nicht in deiner Sprache verfügbar.

The Temperature PID module provides closed-loop PID thermostat control that automatically adjusts fan speed to maintain a target temperature. It creates a climate entity in Home Assistant that acts as a thermostat — set your desired temperature and the controller handles the rest.

PID stands for Proportional-Integral-Derivative, three terms that work together:

  • Proportional (P): Reacts to the current temperature error. The further the temperature is from the target, the faster the fans spin. This is the primary driver of fan speed.
  • Integral (I): Corrects for persistent offset over time. If the P term alone can’t quite reach the target, the I term slowly increases fan speed to close the gap.
  • Derivative (D): Dampens rapid temperature changes. If temperature is rising quickly, the D term increases fan speed preemptively. Usually left at 0 for fan control since temperature changes are slow.

Choose PID when you need precise temperature maintenance with automatic adjustment — for example, keeping a server closet at exactly 25 C. PID is the most capable temperature module but requires more configuration than the alternatives.

For simpler setups, consider Temperature Linear (three-zone mapping) or Temperature Curve (custom multi-point profile). See the modules overview for a comparison table.

VariableDefaultDescription
friendly_name"Fancontroller"Device name prefix for all entities
kp"3.0"Proportional gain (UI scale, x100)
ki"0.005"Integral gain (UI scale, x100)
kd"0.0"Derivative gain (UI scale, x100)
max_integral"0.0"Maximum integral term (0 = unlimited)
output_averaging_samples"1"Output smoothing samples
derivative_averaging_samples"5"Derivative smoothing samples
temperature_sensor_id"fancontroller_temperature"Id of the sensor supplying the control temperature
EntityDescriptionDefault
ThermostatPID-controlled climate entityTarget 30 C, range 20-50 C
EntityRangeStepDefaultDescription
PID kp0 — 500.13.0Proportional gain (UI scale)
PID ki0 — 0.20.0010.005Integral gain (UI scale)
PID kd0 — 20010.0Derivative gain (UI scale)
PID Deadband Threshold Low0 — 5 C0.050.25How far below target before PID reacts
PID Deadband Threshold High0 — 5 C0.050.25How far above target before PID reacts
PID Deadband ki Multiplier0 — 0.20.010.04Ki scaling inside deadband (0.04 = 4% of normal Ki)
Fan Minimum Speed0 — 30%10Minimum fan speed enforced by PID (0 = fans can stop)
EntityUnitDescription
P term%Current proportional contribution
I term%Current integral contribution
D term%Current derivative contribution
Output value%Combined PID output (fan speed)
Error valueCDifference between actual and target temperature
Is in deadband0/1Whether temperature is within deadband range
EntityDefaultDescription
PID Control Fan 1ONEnable PID control for fan 1
PID Control Fan 2ONEnable PID control for fan 2
PID Control Fan 3ONEnable PID control for fan 3
PID Control Fan 4ONEnable PID control for fan 4
EntityDescription
Fan Manual OverrideBypasses PID and sets fan speed directly. Turning off resets the PID integral term.
EntityDescription
PID Climate AutotuneStarts ESPHome’s automatic PID parameter calculation

This block goes in your own configuration, not in the module. See why the assignment lives in the consuming config for the reason. version: 3 is what makes entity grouping work at all; without it, entity grouping has no effect.

web_server:
version: 3
sorting_groups:
- id: grp_temp_pid
name: "PID Temperature Control"
sorting_weight: 160
number:
- id: !extend pid_kp_number
web_server:
sorting_group_id: grp_temp_pid
sorting_weight: 10
- id: !extend pid_ki_number
web_server:
sorting_group_id: grp_temp_pid
sorting_weight: 20
- id: !extend pid_kd_number
web_server:
sorting_group_id: grp_temp_pid
sorting_weight: 30
- id: !extend pid_threshold_low
web_server:
sorting_group_id: grp_temp_pid
sorting_weight: 40
- id: !extend pid_threshold_high
web_server:
sorting_group_id: grp_temp_pid
sorting_weight: 50
- id: !extend pid_ki_multiplier
web_server:
sorting_group_id: grp_temp_pid
sorting_weight: 60
- id: !extend pwm_minimum_pid
web_server:
sorting_group_id: grp_temp_pid
sorting_weight: 70
fan:
- id: !extend manual_fan_control
web_server:
sorting_group_id: grp_temp_pid
sorting_weight: 80
switch:
- id: !extend pid_control_fan1
web_server:
sorting_group_id: grp_temp_pid
sorting_weight: 90
- id: !extend pid_control_fan2
web_server:
sorting_group_id: grp_temp_pid
sorting_weight: 100
- id: !extend pid_control_fan3
web_server:
sorting_group_id: grp_temp_pid
sorting_weight: 110
- id: !extend pid_control_fan4
web_server:
sorting_group_id: grp_temp_pid
sorting_weight: 120
sensor:
- id: !extend p_term
web_server:
sorting_group_id: grp_temp_pid
sorting_weight: 130
- id: !extend i_term
web_server:
sorting_group_id: grp_temp_pid
sorting_weight: 140
- id: !extend d_term
web_server:
sorting_group_id: grp_temp_pid
sorting_weight: 150
- id: !extend o_term
web_server:
sorting_group_id: grp_temp_pid
sorting_weight: 160
- id: !extend e_term
web_server:
sorting_group_id: grp_temp_pid
sorting_weight: 170
- id: !extend in_deadband_term
web_server:
sorting_group_id: grp_temp_pid
sorting_weight: 180
climate:
- id: !extend pid_thermostat
web_server:
sorting_group_id: grp_temp_pid
sorting_weight: 190
button:
- id: !extend pid_autotune
web_server:
sorting_group_id: grp_temp_pid
sorting_weight: 200
packages:
temperature_pid:
url: https://github.com/zeroflow/wifi-fancontroller
ref: main
files:
- path: modules/temperature_pid.yaml
vars:
friendly_name: "My Fan Controller"
packages:
temperature_pid:
url: https://github.com/zeroflow/wifi-fancontroller
ref: main
files:
- path: modules/temperature_pid.yaml
vars:
friendly_name: "Server Rack"
kp: "5.0"
ki: "0.01"
kd: "0.0"
ParameterUI Value (HA)Internal ValueUI Range
kp3.00.030 — 50
ki0.0050.000050 — 0.2
kd0.00.00 — 200

To convert: Internal = UI / 100

Getting started:

  • Set your target temperature in the thermostat entity and observe the behavior for 10-15 minutes
  • Watch the P term sensor in HA — it should react proportionally to temperature changes
  • Watch the I term sensor — it should slowly climb if the P term alone can’t reach the target
  • The output value sensor shows the combined fan speed percentage

When to use Autotune:

  • Press the PID Climate Autotune button to let the controller calculate parameters automatically
  • Autotune works best when the system is at a steady state (fans running, temperature stable)
  • After autotune completes, the new parameters are saved to flash and persist across reboots

Manual tuning:

  • Increase kp if fans react too slowly to temperature changes
  • Increase ki if the temperature settles slightly above or below the target
  • Leave kd at 0 unless you see oscillation — temperature changes are usually slow enough that derivative control isn’t needed

Deadband:

  • The default deadband is +/- 0.25 C around the target temperature
  • Inside the deadband, Ki is scaled to 4% of its normal value (ki_multiplier = 0.04), which prevents integral windup while allowing slow drift correction
  • Increase the deadband thresholds if fans cycle on/off too frequently near the target

Based on work by patrickcollins12/esphome-fan-controller.

By default the module reads fancontroller_temperature, the onboard HDC1080 that every hardware package declares. Set temperature_sensor_id to the id of any other temperature sensor in your configuration to control from that instead:

packages:
# modules/bme680.yaml is not standalone - it needs the shared I2C bus (bus_a)
# that your hardware package declares. Use your own board revision here.
hardware:
url: https://github.com/zeroflow/wifi-fancontroller
ref: main
files: [hardware-rev-3.1.yaml]
bme680:
url: https://github.com/zeroflow/wifi-fancontroller
ref: main
files: [modules/bme680.yaml]
temperature_pid:
url: https://github.com/zeroflow/wifi-fancontroller
ref: main
files:
- path: modules/temperature_pid.yaml
vars:
temperature_sensor_id: "bme680_temperature"

This becomes the climate component’s process variable, so the thermostat’s current temperature is the external sensor’s reading.

Retune after switching sensors. A sensor at the end of a cable responds more slowly to fan changes than the onboard sensor does, and that added lag changes the gains that behave well — start by reducing kp and re-checking for oscillation.

Omit the variable and it falls back to fancontroller_temperature, so existing configurations keep working unchanged. A misspelled id fails the build rather than quietly falling back to the onboard sensor.

The Qwiic modules build their sensor ids from an id-prefix substitution, so BME680 gives you bme680_temperature, SCD41 gives scd41_temperature, and DS2484 gives whatever ids you assign your own dallas_temp sensors.

If you also run the SSD1306 OLED module, note that it displays the onboard sensor regardless of this setting, so the screen and the fan control can show different temperatures.

The module names the sensor it is reading in its boot log, so you can confirm from a log dump that the override took effect.