Vary a PWM output based on the temperature

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 

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

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

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 :slight_smile:
Thanks

it’s not same. Output doesn’t have speed while speed fan has.

See the docs for the correct action - set_level

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.

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.

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

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.

True.
And speed vs pwm in not linear, not even with Noctua.

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 :slight_smile:

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

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 …