Skip to content

Temperature Curve

The Temperature Curve module lets you define a custom 5-point temperature-to-speed curve for granular control over fan behavior. Values between points are linearly interpolated, giving you a smooth speed transition across the entire temperature range.

Choose Curve when you need more control points than the linear module’s three zones but don’t need PID’s automatic target-chasing behavior. It’s ideal when you know exactly how you want fans to respond at specific temperatures.

For simpler threshold-based control, see Temperature Linear. For automatic temperature targeting, see Temperature PID. The modules overview has a comparison table.

VariableDefaultDescription
friendly_name"Fancontroller"Device name prefix for all entities
curve_temp1"20.0"Temperature point 1 (C)
curve_speed1"0.0"Fan speed at point 1 (%)
curve_temp2"25.0"Temperature point 2 (C)
curve_speed2"25.0"Fan speed at point 2 (%)
curve_temp3"30.0"Temperature point 3 (C)
curve_speed3"50.0"Fan speed at point 3 (%)
curve_temp4"40.0"Temperature point 4 (C)
curve_speed4"75.0"Fan speed at point 4 (%)
curve_temp5"50.0"Temperature point 5 (C)
curve_speed5"100.0"Fan speed at point 5 (%)
temperature_sensor_id"fancontroller_temperature"Id of the sensor supplying the control temperature

The five temperature/speed pairs define a piecewise linear curve:

  1. Each pair sets a “point” on the curve (e.g., 25 C = 25% speed)
  2. Between any two adjacent points, the fan speed is linearly interpolated
  3. Below the lowest temperature point, fans run at that point’s speed
  4. Above the highest temperature point, fans run at that point’s speed

Auto-sorting: Temperature points are automatically sorted at runtime, so they work even if defined out of order. However, the module validates the curve at boot and logs a warning if points are not monotonically increasing.

EntityRangeStepUnitDefaultDescription
Curve Temperature 10 — 600.5C20.0Temperature point 1
Curve Speed 10 — 1001%0Fan speed at point 1
Curve Temperature 20 — 600.5C25.0Temperature point 2
Curve Speed 20 — 1001%25Fan speed at point 2
Curve Temperature 30 — 600.5C30.0Temperature point 3
Curve Speed 30 — 1001%50Fan speed at point 3
Curve Temperature 40 — 600.5C40.0Temperature point 4
Curve Speed 40 — 1001%75Fan speed at point 4
Curve Temperature 50 — 600.5C50.0Temperature point 5
Curve Speed 50 — 1001%100Fan speed at point 5
EntityUnitDescription
Curve Output%Current interpolated fan speed (updated every 10s)
EntityDefaultDescription
Auto Control Fan 1ONEnable curve control for fan 1
Auto Control Fan 2ONEnable curve control for fan 2
Auto Control Fan 3ONEnable curve control for fan 3
Auto Control Fan 4ONEnable curve control for fan 4
EntityCategoryDescription
Curve Configuration WarningDiagnosticON if temperature points are not monotonically increasing

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_curve
name: "Temperature Curve"
sorting_weight: 110
number:
- id: !extend curve_temp1
web_server:
sorting_group_id: grp_temp_curve
sorting_weight: 10
- id: !extend curve_speed1
web_server:
sorting_group_id: grp_temp_curve
sorting_weight: 20
- id: !extend curve_temp2
web_server:
sorting_group_id: grp_temp_curve
sorting_weight: 30
- id: !extend curve_speed2
web_server:
sorting_group_id: grp_temp_curve
sorting_weight: 40
- id: !extend curve_temp3
web_server:
sorting_group_id: grp_temp_curve
sorting_weight: 50
- id: !extend curve_speed3
web_server:
sorting_group_id: grp_temp_curve
sorting_weight: 60
- id: !extend curve_temp4
web_server:
sorting_group_id: grp_temp_curve
sorting_weight: 70
- id: !extend curve_speed4
web_server:
sorting_group_id: grp_temp_curve
sorting_weight: 80
- id: !extend curve_temp5
web_server:
sorting_group_id: grp_temp_curve
sorting_weight: 90
- id: !extend curve_speed5
web_server:
sorting_group_id: grp_temp_curve
sorting_weight: 100
sensor:
- id: !extend curve_output_value
web_server:
sorting_group_id: grp_temp_curve
sorting_weight: 110
switch:
- id: !extend auto_control_fan1
web_server:
sorting_group_id: grp_temp_curve
sorting_weight: 120
- id: !extend auto_control_fan2
web_server:
sorting_group_id: grp_temp_curve
sorting_weight: 130
- id: !extend auto_control_fan3
web_server:
sorting_group_id: grp_temp_curve
sorting_weight: 140
- id: !extend auto_control_fan4
web_server:
sorting_group_id: grp_temp_curve
sorting_weight: 150
binary_sensor:
- id: !extend curve_config_warning_sensor
web_server:
sorting_group_id: grp_temp_curve
sorting_weight: 900
packages:
temperature_curve:
url: https://github.com/zeroflow/wifi-fancontroller
ref: main
files:
- path: modules/temperature_curve.yaml
vars:
friendly_name: "My Fan Controller"

Prioritizes low noise. Fans stay off until 25 C and ramp slowly, reaching only 80% at 55 C. Best for quiet home office environments where some temperature rise is acceptable.

packages:
temperature_curve:
url: https://github.com/zeroflow/wifi-fancontroller
ref: main
files:
- path: modules/temperature_curve.yaml
vars:
friendly_name: "Silent Cooler"
curve_temp1: "20.0"
curve_speed1: "0.0"
curve_temp2: "25.0"
curve_speed2: "20.0"
curve_temp3: "35.0"
curve_speed3: "40.0"
curve_temp4: "45.0"
curve_speed4: "60.0"
curve_temp5: "55.0"
curve_speed5: "80.0"

Good all-around curve that balances noise and cooling. This is the default configuration.

packages:
temperature_curve:
url: https://github.com/zeroflow/wifi-fancontroller
ref: main
files:
- path: modules/temperature_curve.yaml
vars:
friendly_name: "Balanced Cooler"
curve_temp1: "20.0"
curve_speed1: "0.0"
curve_temp2: "25.0"
curve_speed2: "25.0"
curve_temp3: "30.0"
curve_speed3: "50.0"
curve_temp4: "40.0"
curve_speed4: "75.0"
curve_temp5: "50.0"
curve_speed5: "100.0"

Reacts quickly to temperature increases with steep ramp. Reaches full speed at 40 C. Best for high-performance setups where cooling takes priority over noise.

packages:
temperature_curve:
url: https://github.com/zeroflow/wifi-fancontroller
ref: main
files:
- path: modules/temperature_curve.yaml
vars:
friendly_name: "Aggressive Cooler"
curve_temp1: "20.0"
curve_speed1: "0.0"
curve_temp2: "22.0"
curve_speed2: "30.0"
curve_temp3: "28.0"
curve_speed3: "60.0"
curve_temp4: "35.0"
curve_speed4: "85.0"
curve_temp5: "40.0"
curve_speed5: "100.0"

Always-on baseline cooling with rapid ramp. Fans never fully stop (20% minimum at 15 C), reaching full speed at just 35 C. Designed for server racks and network closets.

packages:
temperature_curve:
url: https://github.com/zeroflow/wifi-fancontroller
ref: main
files:
- path: modules/temperature_curve.yaml
vars:
friendly_name: "Server Rack"
curve_temp1: "15.0"
curve_speed1: "20.0"
curve_temp2: "20.0"
curve_speed2: "30.0"
curve_temp3: "25.0"
curve_speed3: "50.0"
curve_temp4: "30.0"
curve_speed4: "75.0"
curve_temp5: "35.0"
curve_speed5: "100.0"
  • Watch the Configuration Warning sensor — if it shows ON in Home Assistant, your temperature points are not in ascending order. The module still works (points are auto-sorted at runtime), but it’s best practice to define them in order for clarity.
  • Define temperatures in ascending order — while auto-sorting handles out-of-order points, having them in order makes your configuration easier to read and debug.
  • Start with the Balanced preset — the default curve works well for most scenarios. Adjust individual points based on your environment.
  • Consider your fans’ minimum speed — if your fans need at least 20% PWM to spin reliably, make sure your lowest non-zero speed point is at or above that threshold.
  • Use the Curve Output sensor for debugging — it updates every 10 seconds and shows the current interpolated fan speed, making it easy to verify your curve behaves as expected at different temperatures.

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_curve:
url: https://github.com/zeroflow/wifi-fancontroller
ref: main
files:
- path: modules/temperature_curve.yaml
vars:
temperature_sensor_id: "bme680_temperature"

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.