Hello,
I would like to vary a PWM output based on the temperature of an input sensor.
I’m using an ESP32 S3 with ESPHome.
My temperature sensor is an Atlas EZO-RTD.
I’ve successfully calibrated it to a temperature of 30°C.
I would like, for example, that if the temperature on my EZO-RTD changes from 20°C to 30°C, the output voltage of my PWM should go from 0 to 100%.
I can manage a PWM output with Platform:ledc, but I don’t know how to link rtd_ezo and sortie_pwm_temp to handle this proportionality.
Can someone guide me?
Thank you.
Cédric
# Temperature sensor declaration
- platform: ezo
id: rtd_ezo
name: "Temperature Measurement"
address: 102
accuracy_decimals: 2
unit_of_measurement: "°C"
update_interval: 10s
# export temperature for automaton
output:
- platform: ledc
pin: GPIO0
id: sortie_pwm_temp
frequency: 1000 hz
fan:
- platform: speed
output: sortie_pwm_temp
name: "sortie_pwm_temp"
id: pwm_temp
Karosm
(Karosm)
December 1, 2025, 10:12pm
2
Really minimal approach would be:
- platform: ezo
id: rtd_ezo
name: "Temperature Measurement"
address: 102
accuracy_decimals: 2
unit_of_measurement: "°C"
update_interval: 10s
on_value:
then:
- lambda: |-
float speed = (x - 20.0) / 10.0;
id(pwm_temp).set_speed(speed);
A somewhat more sophisticated approach would be to use the climate component. Here’s an example I use to run a fan that cools an enclosure with some electronics:
climate:
- platform: pid
name: "PID Climate Controller"
sensor: temperature_sensor
default_target_temperature: 35°C
cool_output: fan_speed
visual:
min_temperature: 25
max_temperature: 40
temperature_step: 0.1
control_parameters:
kp: 0.01
ki: 0.001
kd: 0.0
output_averaging_samples: 5 # smooth the output
derivative_averaging_samples: 5 # smooth the derivative value
output:
- platform: ledc
id: fan_speed
pin: GPIO4
frequency: 25kHz
min_power: 50%
max_power: 100%
zero_means_zero: true
2 Likes
Good evening,
Thanks for sharing, but I need to implement a solution using floats to track the voltage changes from my temperature sensor. Thresholds won’t work.
I tried using LEDC, but floats aren’t supported, and my skills are limited.
If you have any ideas…
In any case, thanks for your help.
Cédric
@karosm ,
Your idea sounds great, but it generates an error with LEDC, which doesn’t handle floats.
Actually, I just want to copy an analog value proportionally to a PWM output.
- platform: ezo
id: rtd_ezo
name: "Temperature Measurement"
address: 102
accuracy_decimals: 2
unit_of_measurement: "°C"
update_interval: 10s
on_value:
then:
- lambda: |-
float speed = (x - 20.0) / 10.0;
id(pwm_temp).set_speed(speed);
Cédric
Karosm
(Karosm)
December 3, 2025, 6:25pm
5
The code was setting speed fan, not directly LEDC. And LEDC surely asks float.
Post the error you got.
Why? What is the actual problem you are trying to solve?
Thresholds won’t work
There was no mention of thresholds in any of the responses.
hello,
the code is like this
- platform: ezo
id: rtd_ezo
name: "Temperature Measurement"
address: 102
accuracy_decimals: 2
unit_of_measurement: "°C"
update_interval: 10s
on_value:
then:
- lambda: |-
float speed = (x - 20.0) / 10.0;
id(sortie_pwm_temp).set_speed(speed);
output:
- platform: ledc
pin: GPIO0
id: sortie_pwm_temp
frequency: 1000 hz
fan:
- platform: speed
output: sortie_pwm_temp
name: "sortie_pwm_temp"
id: pwm_temp
@Karosm the error is
/config/esphome/esp.yaml: In lambda function:
/config/esphome/esp.yaml:375:24: error: 'class esphome::ledc::LEDCOutput' has no member named 'set_speed'
375 | id(sortie_pwm_temp).set_speed(speed);
| ^~~~~~~~~
@Clydebarrow
I simply want to copy an analog value (temperature) proportionally to a PWM output to send to a TSX PLC.
thanks both
Karosm
(Karosm)
December 3, 2025, 8:29pm
8
Why you changed it from pwm_temp to sortie_pwm_temp?
It is the same with sortie_pwm_temp and pwm_temp.
The name has change because i test a lot of code
Thanks
Karosm
(Karosm)
December 3, 2025, 9:00pm
10
it’s not same. Output doesn’t have speed while speed fan has.
clydebarrow
(Clyde Stubbs)
December 3, 2025, 10:27pm
11
See the docs for the correct action - set_level
cedrict
December 4, 2025, 1:30am
12
Can you show me how to write this?
Like this?
id(sortie_pwm_temp).set_action(speed);
Thanks
If you didn’t read the docs as linked above which have the answer, and somehow interpreted set_level as set_action I don’t know how else to help you.
Karosm
(Karosm)
December 4, 2025, 7:15am
14
If you want to control the output (id: sortie_pwm_temp) directly:
- lambda: |-
float speed = (x - 20.0) / 10.0;
id(sortie_pwm_temp).set_level(speed);
If you want to control the speed fan (id: pwm_temp) which eventually controls the output:
- lambda: |-
int speed = ((x - 20.0) / 10.0) * 100.0;
auto call = id(pwm_temp).make_call();
call.set_speed(speed);
call.perform();
I verified the library, call method is needed for fan.
Also, try to name your components way they don’t cause confusion, right now you have output with id which is also name of the fan.
If you decide to go one way or other, we’ll add some clamping to the code.
hilt_ctn
(hilt_ctn)
December 4, 2025, 11:30am
15
I adapted this from one of my ESP32’s that uses the output of a Dallas sensor to vary the speed of a PWM enabled fan. Give it whirl ( fan humour ). Don’t forget to change the the variables like names, board and GPIO pins to match your setup
substitutions:
temperature_threshold_low: "20"
temperature_threshold_high: "30"
minimum_fan_speed: "25"
esphome:
name: esphome-test
friendly_name: Esphome Test
esp32:
board: esp32dev
framework:
type: arduino
logger:
ota:
platform: esphome
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
ap:
ssid: "Esphome-Test Fallback Hotspot"
password: "ZXkAzK0H1uAj"
i2c:
sda: GPIO17
scl: GPIO18
scan: true
id: bus_a
#-------------------------------------------------------------------------------
# Sensors
#-------------------------------------------------------------------------------
sensor:
- platform: ezo
id: rtd_ezo
name: "Temperature Measurement"
address: 102
accuracy_decimals: 2
unit_of_measurement: "°C"
update_interval: 10s
on_value:
then:
- script.execute: set_fan_state
#-------------------------------------------------------------------------------
# Fan
#-------------------------------------------------------------------------------
output:
- platform: ledc
pin: GPIO21
id: sortie_pwm_temp
frequency: 22000 Hz
fan:
- platform: speed
output: sortie_pwm_temp
name: "sortie_pwm_temp"
id: pwm_temp
#-------------------------------------------------------------------------------
# Script
#-------------------------------------------------------------------------------
script:
- id: set_fan_state
then:
- if:
condition:
lambda: |-
return id(rtd_ezo).state < ${temperature_threshold_low};
then:
- fan.turn_off: pwm_temp
else:
- fan.turn_on:
id: pwm_temp
speed: !lambda |-
float temp = id(rtd_ezo).state;
float low = ${temperature_threshold_low};
float high = ${temperature_threshold_high};
float min_speed = ${minimum_fan_speed};
if (temp >= high) {
ESP_LOGD("Fan speed calc", "Above upper threshold → max speed");
return 100;
}
float calc_speed =
((100 - min_speed) / (high - low)) *
(temp - low) +
min_speed;
ESP_LOGD("Fan speed calc", "Calculated speed = %f", calc_speed);
return calc_speed;
#-------------------------------------------------------------------------------
# Button
#-------------------------------------------------------------------------------
button:
- platform: restart
name: "Reboot"
1 Like
hilt_ctn
(hilt_ctn)
December 4, 2025, 1:09pm
16
Just a heads up on fan hardware if you’re not aware.
Not all PWM enabled fans spin down to zero i.e. full stop. It depends on make and model. Most have an minimum speed of around 25% - 33% even when set to 0%. Some Noctua’s do spin down to zero. I have both my environment due to differing requirements as to what the fans are cooling.
Karosm
(Karosm)
December 4, 2025, 2:58pm
17
True.
And speed vs pwm in not linear, not even with Noctua.
cedrict
December 4, 2025, 8:09pm
18
Good evening everyone,
Thank you so much for your help!
Sorry for the silly way I copied the code; I’m French and I wrote this in the middle of the night—clearly a bad idea
This code works perfectly for my needs! Great!
- lambda: |-
float speed = (x - 20.0) / 10.0;
id(sortie_pwm_temp).set_level(speed);
But now I need to set limits on it, like a minimum of 0% and a maximum of 50% of the PWM.
Can you help me?
thanks
Cedric
Karosm
(Karosm)
December 4, 2025, 8:33pm
19
You were doing it directly with output in that case.
You can add parameters to the output:
min_power: 0.0
max_power: 0.5
But I’m not sure what you are really looking for …