ESPHome fan controller issue on boot, initial fan speed based upon temp range

So, the problem I have is that that fan speed does not fall into regulation based on the temperature ranges specified until the unit moves from one range to another. I set the preliminary speed to 100% until that happens at boot. I have been trying to find a way set set a bogus value into the temperature value so the code will quickly see a window change and start regulating speed, I have had no luck. Can this even be done?

substitutions:
  name: apple-fan
  friendly_name: Apple Fan Controller
  devicename: apple-fan
  esphome_platform: esp32
  esphome_board: esp32dev
  esphome_project_name: "Apple.temperature_control"
  esphome_project_version: "apple-fan-v.1"

esphome:
  name: "apple-fan"
  friendly_name: Apple Fan Controller
  name_add_mac_suffix: false
  project:
    name: $esphome_project_name
    version: $esphome_project_version
  on_boot:
# Power up fan at boot
    priority: 800
    then:
      - if:
          condition:
            api.connected: null
          then:
            - logger.log: API is connected! Now we can trigger what we want!
          else: 
            - delay: 1s
            - output.set_level:
                id: apple_pwm_speed
                level: 1.0 # 100% pwm speed until temp is taken
  

esp32:
  board: esp32dev
  framework:
    type: esp-idf

# Enable logging
logger: 
  level: debug
  logs:
    sht3xd.component: info

# Enable Home Assistant API
api:
  reboot_timeout: 0s

# Allow Over-The-Air updates
ota:
- platform: esphome

wifi:
  domain: .home
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  # Set up a wifi access point
  ap:  
    ssid: "${friendly_name}"
    password: "12345678"

dashboard_import:
  package_import_url: github://esphome/example-configs/esphome-web/esp32.yaml@main
  import_full_config: true

web_server:
  include_internal: True

status_led:
  pin:
     number: GPIO2
     inverted: false

i2c:
  - id: bus_a
    sda: GPIO21
    scl: GPIO22
    scan: true
    frequency: 100kHz
  - id: bus_b
    sda: GPIO16
    scl: GPIO17
    scan: true
    frequency: 100kHz

#display:
#  - platform: lcd_pcf8574
#    id: mydisplay
#    dimensions: 16x2
#    i2c_id: bus_b
#    address: 0x27

output:
  - platform: ledc
    id: apple_pwm_speed
    pin: GPIO27
    frequency: 25000Hz

number:
  - platform: template
    name: "Pwm Fan Slider"
    id: slider
    min_value: 15
    max_value: 100
    step: 1
    optimistic: true
    unit_of_measurement: "%"
    set_action:
      then:
        - output.set_level:
            id: apple_pwm_speed
            level: !lambda "return x/100;"
text_sensor:

  # Send IP Address                                                                                                                                                                                                                      
  - platform: wifi_info
    ip_address:
      name: $friendly_name IP Address

  # Send Uptime in raw seconds                                                                                                                                                                                                           
  - platform: template
    name: $friendly_name Uptime
    id: uptime_human
    icon: mdi:clock-start        
sensor:
  - platform: sht3xd
    temperature:
      name: "Apple Internal Temperature"
      id: sht30t
      internal: false
      accuracy_decimals: 1
      filters: 
        - lambda: return x * (9.0/5.0) + 32.0;
      unit_of_measurement: "°F"
#      on_value:
#        then:
#          - logger.log: "testing..."
      on_value_range:
        - above: 74.0
          then:
            - logger.log: "Set fan level 100 over 72"
            - number.set:
                id: slider
                value: 100
            - output.set_level:
                id: apple_pwm_speed
                level: 100%
        - above: 72.0
          below: 74
          then:
            - logger.log: "Set fan level 66 for above 68 below 72"
            - number.set:
                id: slider
                value: 75
            - output.set_level:
                id: apple_pwm_speed
                level: 75%
        - above: 68.0
          below: 72
          then:
            - logger.log: "Set fan level 66 for above 68 below 72"
            - number.set:
                id: slider
                value: 66
            - output.set_level:
                id: apple_pwm_speed
                level: 66%
        - above: 65.0
          below: 68.0
          then:
            - logger.log: "Set fan level 33 for above 65 below 68"
            - number.set:
                id: slider
                value: 33
            - output.set_level:
                id: apple_pwm_speed
                level: 33%
        - above: 63.0
          below: 65.0
          then:
            - logger.log: "Set fan level 66 for above 65 below 63"
            - number.set:
                id: slider
                value: 25
            - output.set_level:
                id: apple_pwm_speed
                level: 25%
 
        - below: 63.0
          then:
            - logger.log: "Set fan level 15 below 63F"
            - number.set:
                id: slider
                value: 15
            - output.set_level:
                id: apple_pwm_speed
                level: 15%
    humidity:
      name: "Apple Internal Humidity"
      id: sht30h
      internal: false
      accuracy_decimals: 0
      filters: 
        - sliding_window_moving_average:
            window_size: 15
            send_every: 15
    i2c_id: bus_a
    address: 0x44
    update_interval: 30s

  # Fan Speed
  - platform: pulse_counter
    pin:
      number: GPIO25
      mode:
        input: true
        pullup: true
    name: PWM Fan RPM
    id: fan_pulse
    unit_of_measurement: 'RPM'
    internal: false
    accuracy_decimals: 1
    filters:
      - multiply: 0.5
    count_mode:
      rising_edge: INCREMENT
      falling_edge: DISABLE
    update_interval: 3s
   # Send WiFi signal strength & uptime to HA                                                                                                                                                                                             
  - platform: wifi_signal
    name: $friendly_name WiFi Strength
    update_interval: 60s
  - platform: uptime
    name: $friendly_name Uptime
    id: uptime_sensor
    update_interval: 60s
    on_raw_value:
      then:
        - text_sensor.template.publish:
            id: uptime_human
            # Custom C++ code to generate the result                                                                                                                                                                                     
            state: !lambda |-
              int seconds = round(id(uptime_sensor).raw_state);
              int days = seconds / (24 * 3600);                                                                                                                                                                                          
              seconds = seconds % (24 * 3600);                                                                                                                                                                                           
              int hours = seconds / 3600;                                                                                                                                                                                                
              seconds = seconds % 3600;                                                                                                                                                                                                  
              int minutes = seconds /  60;                                                                                                                                                                                               
              seconds = seconds % 60;                                                                                                                                                                                                    
              return (                                                                                                                                                                                                                   
                (days ? to_string(days) + "d " : "") +
                (hours ? to_string(hours) + "h " : "") +
                (minutes ? to_string(minutes) + "m " : "") +
                (to_string(seconds) + "s")
              ).c_str();
switch:
  - platform: restart
    name: "Restart"

I use on_value (instead of on_value_range) to run a script that keeps the speed 15% above the temperature, if the temperature is 60ºC the speed will be 75%.
Maybe you can adapt it to your needs

script:
  - id: check_sensors
    then:
      - lambda: |-
          int delta_fan = 15;
          int temp = ceil(id(ryzen_7_5700u).state);
          if (id(ryzen_power).state == 1) { // Check if the PC is turned on
          if (temp < (100 - delta_fan )) { // If the temperature is less than 85º C, set the speed to temperature + delta
              auto call = id(ryzen_fan_speed).make_call();
              call.set_value(temp + delta_fan);
              call.perform();
            } else { // If the temperature is higher than 85º C, set the speed to 100%
              auto call = id(ryzen_fan_speed).make_call();
              call.set_value(100);
              call.perform();
            }
          } else { // If the PC is turned off, set the speed to 25%
            auto call = id(ryzen_fan_speed).make_call();
            call.set_value(25);
            call.perform();
          }

I like this idea, but unfortunately while I can see what your script does and it is pretty awesome… I just can’t seem to make it work. There is something I really do not understand about calling or starting the script. Have never used one before.

This:

sensor:
  - platform: sht3xd
    temperature:
      name: "Apple Internal Temperature"
      id: sht30t
      internal: false
      accuracy_decimals: 1
      filters: 
        - lambda: return x * (9.0/5.0) + 32.0;
      unit_of_measurement: "°F"
      on_value:
        then:
          - script.execute: check_sensors

edit:
The script is just the way I used it.
You don’t need the script, you can put the code right after on_value.

So, I think I am close. But it appears the pwm control with LEDC does not support the make_call directive. I am not aware of another way to set up pwm output control…

/config/esphome/esphome-web-8281ec.yaml: In lambda function:
/config/esphome/esphome-web-8281ec.yaml:277:40: error: 'class esphome::ledc::LEDCOutput' has no member named 'make_call'
  277 |               auto call = id(apple_pwm_speed).make_call();
      |                                        ^~~~~~~~~
/config/esphome/esphome-web-8281ec.yaml:281:40: error: 'class esphome::ledc::LEDCOutput' has no member named 'make_call'
  281 |               auto call = id(apple_pwm_speed).make_call();

You control the slider, not the ledc.

substitutions:
  name: apple-fan
  friendly_name: Apple Fan Controller
  devicename: apple-fan
  esphome_platform: esp32
  esphome_board: esp32dev
  esphome_project_name: "Apple.temperature_control"
  esphome_project_version: "apple-fan-v.1"

esphome:
  name: "apple-fan"
  friendly_name: Apple Fan Controller
  name_add_mac_suffix: false
  project:
    name: $esphome_project_name
    version: $esphome_project_version
  on_boot:
# Power up fan at boot
    priority: 800
    then:
      - if:
          condition:
            api.connected: null
          then:
            - logger.log: API is connected! Now we can trigger what we want!
          else: 
            - delay: 1s
            - output.set_level:
                id: apple_pwm_speed
                level: 1.0 # 100% pwm speed until temp is taken
  

esp32:
  board: esp32dev
  framework:
    type: esp-idf

# Enable logging
logger: 
  level: debug
  logs:
    sht3xd.component: info

# Enable Home Assistant API
api:
  reboot_timeout: 0s

# Allow Over-The-Air updates
ota:
- platform: esphome

wifi:
  domain: .home
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  # Set up a wifi access point
  ap:  
    ssid: "${friendly_name}"
    password: "12345678"

dashboard_import:
  package_import_url: github://esphome/example-configs/esphome-web/esp32.yaml@main
  import_full_config: true

web_server:
  include_internal: True

status_led:
  pin:
     number: GPIO2
     inverted: false

i2c:
  - id: bus_a
    sda: GPIO21
    scl: GPIO22
    scan: true
    frequency: 100kHz
  - id: bus_b
    sda: GPIO16
    scl: GPIO17
    scan: true
    frequency: 100kHz

#display:
#  - platform: lcd_pcf8574
#    id: mydisplay
#    dimensions: 16x2
#    i2c_id: bus_b
#    address: 0x27

output:
  - platform: ledc
    id: apple_pwm_speed
    pin: GPIO27
    frequency: 25000Hz

number:
  - platform: template
    name: "Pwm Fan Slider"
    id: slider
    min_value: 15
    max_value: 100
    step: 1
    optimistic: true
    unit_of_measurement: "%"
    set_action:
      then:
        - output.set_level:
            id: apple_pwm_speed
            level: !lambda "return x/100;"
text_sensor:

  # Send IP Address                                                                                                                                                                                                                      
  - platform: wifi_info
    ip_address:
      name: $friendly_name IP Address

  # Send Uptime in raw seconds                                                                                                                                                                                                           
  - platform: template
    name: $friendly_name Uptime
    id: uptime_human
    icon: mdi:clock-start        
sensor:
  - platform: sht3xd
    temperature:
      name: "Apple Internal Temperature"
      id: sht30t
      internal: false
      accuracy_decimals: 1
      filters: 
        - lambda: return x * (9.0/5.0) + 32.0;
      unit_of_measurement: "°F"
      on_value:
        then:
          - lambda: |-
              int delta_fan = 15;
              int temp = ceil(x);
              if (temp < (100 - delta_fan )) { // If the temperature is less than 85º C, set the speed to temperature + delta
                  auto call = id(slider).make_call();
                  call.set_value(temp + delta_fan);
                  call.perform();
                } else { // If the temperature is higher than 85º C, set the speed to 100%
                  auto call = id(slider).make_call();
                  call.set_value(100);
                  call.perform();
                }
              


#          - logger.log: "testing..."
      # on_value_range:
      #   - above: 74.0
      #     then:
      #       - logger.log: "Set fan level 100 over 72"
      #       - number.set:
      #           id: slider
      #           value: 100
      #       - output.set_level:
      #           id: apple_pwm_speed
      #           level: 100%
      #   - above: 72.0
      #     below: 74
      #     then:
      #       - logger.log: "Set fan level 66 for above 68 below 72"
      #       - number.set:
      #           id: slider
      #           value: 75
      #       - output.set_level:
      #           id: apple_pwm_speed
      #           level: 75%
      #   - above: 68.0
      #     below: 72
      #     then:
      #       - logger.log: "Set fan level 66 for above 68 below 72"
      #       - number.set:
      #           id: slider
      #           value: 66
      #       - output.set_level:
      #           id: apple_pwm_speed
      #           level: 66%
      #   - above: 65.0
      #     below: 68.0
      #     then:
      #       - logger.log: "Set fan level 33 for above 65 below 68"
      #       - number.set:
      #           id: slider
      #           value: 33
      #       - output.set_level:
      #           id: apple_pwm_speed
      #           level: 33%
      #   - above: 63.0
      #     below: 65.0
      #     then:
      #       - logger.log: "Set fan level 66 for above 65 below 63"
      #       - number.set:
      #           id: slider
      #           value: 25
      #       - output.set_level:
      #           id: apple_pwm_speed
      #           level: 25%
 
      #   - below: 63.0
      #     then:
      #       - logger.log: "Set fan level 15 below 63F"
      #       - number.set:
      #           id: slider
      #           value: 15
      #       - output.set_level:
      #           id: apple_pwm_speed
      #           level: 15%
    humidity:
      name: "Apple Internal Humidity"
      id: sht30h
      internal: false
      accuracy_decimals: 0
      filters: 
        - sliding_window_moving_average:
            window_size: 15
            send_every: 15
    i2c_id: bus_a
    address: 0x44
    update_interval: 30s

  # Fan Speed
  - platform: pulse_counter
    pin:
      number: GPIO25
      mode:
        input: true
        pullup: true
    name: PWM Fan RPM
    id: fan_pulse
    unit_of_measurement: 'RPM'
    internal: false
    accuracy_decimals: 1
    filters:
      - multiply: 0.5
    count_mode:
      rising_edge: INCREMENT
      falling_edge: DISABLE
    update_interval: 3s
   # Send WiFi signal strength & uptime to HA                                                                                                                                                                                             
  - platform: wifi_signal
    name: $friendly_name WiFi Strength
    update_interval: 60s
  - platform: uptime
    name: $friendly_name Uptime
    id: uptime_sensor
    update_interval: 60s
    on_raw_value:
      then:
        - text_sensor.template.publish:
            id: uptime_human
            # Custom C++ code to generate the result                                                                                                                                                                                     
            state: !lambda |-
              int seconds = round(id(uptime_sensor).raw_state);
              int days = seconds / (24 * 3600);                                                                                                                                                                                          
              seconds = seconds % (24 * 3600);                                                                                                                                                                                           
              int hours = seconds / 3600;                                                                                                                                                                                                
              seconds = seconds % 3600;                                                                                                                                                                                                  
              int minutes = seconds /  60;                                                                                                                                                                                               
              seconds = seconds % 60;                                                                                                                                                                                                    
              return (                                                                                                                                                                                                                   
                (days ? to_string(days) + "d " : "") +
                (hours ? to_string(hours) + "h " : "") +
                (minutes ? to_string(minutes) + "m " : "") +
                (to_string(seconds) + "s")
              ).c_str();
switch:
  - platform: restart
    name: "Restart"

OMG, that makes so much sense now. BTW that is a brilliant bit of code. I will test in on Thursday evening. Thanks for your patience.

Sometimes it actually makes more sense to compare running an on_boot vs. a regular automation and not unnecessarily overcomplicate things that dont need to be made complicated.

Once it fully boots up, the temp sensor will be polled and retrieve a value. Putting the action for the condition and your fan would work just the same and doesnt require any unnecessary on_boot automations to create and troubleshoot. You’re really only gaining maybe 500ms faster by putting it under on_boot and idk, to me it doesnt appear to be needed.

So, I did not realize that you reposted my code modified and tonight I figured out how to make your code work properly. The key was when you mentioned the slider!

But, I have a question as I change values and see the results. I do not understand C++ and I understand the lambdas are based on it. I do not understand the following line:

int temp = ceil(x);

Where is the value of x or what sets the value of x. I ask because I would like to move the bottom window of the operation and it seems to me that the bottom is not defined like the top is not defined either.

I am trying to figure out how to for example have the fan run at 15% at 70F or below and then ramp to full speed at say 85F.

So, I set delta_fan = 1 and now at room temp it runs 1% higher speed. I tried doing math on the value in delta_fan and attempted to multiply the value of the temperature sensor by 0.5, but that chokes during compile.

X is the sensor value, this comes from the esphome code. The same as in your filter when you convert to Fahrenheit.

        - lambda: return x * (9.0/5.0) + 32.0;

Try this:

      on_value:
        then:
          - lambda: |-
              int min_temp = 70; // Adjust here
              int max_temp = 85; // Adjust here
              int mim_speed = 15; // Adjust here
              int max_speed = 100; // Adjust here
              int temp = floor(x);
              int speed = floor(mim_speed + ((temp - min_temp) * (max_speed - mim_speed) / (max_temp - min_temp))); 
              if (temp <= (min_temp)) { // If the temperature is less than 70º F, set the speed to 15%
                auto call = id(slider).make_call();
                call.set_value(mim_speed);
                call.perform();
                } else if (temp < (max_temp)) { // If the temperature is less than 85º F, set the speed to XX%
                auto call = id(slider).make_call();
                call.set_value(speed);
                call.perform();
                } else { // If the temperature is higher than 85º C, set the speed to 100%
                auto call = id(slider).make_call();
                call.set_value(100);
                call.perform();
              } 

70º or less the speed will be 15%, 85º or more will be 100% and for each degree the temperature will increase by about 5% in speed

That does exactly the right thing! Sorry that I am not good at coding, but each time I take on a new idea I learn more. My old brain is not a open to learning and learn by example. I have dug into this control so much, thought I had it figured out… but obviously did not. I will dissect this code and experiment with it and hopefully learn quite a bit about it and options to use going forward.

Thanks!

No problem, I’m not either.
I’ve just learned a few things along the automation/coding journey.

A small improvement in the code.
Using clamp or constrain instead of IF and ELSE

      on_value:
        then:
          lambda: |-
            // Define temperature and fan speed limits
            const int min_temp = 70;   // Minimum temperature threshold (°F)
            const int max_temp = 85;   // Maximum temperature threshold (°F)
            const int min_speed = 15;  // Minimum fan speed percentage (%)
            const int max_speed = 100; // Maximum fan speed percentage (%)

            // Create a call to update the fan speed
            auto call = id(slider).make_call();

            // Get the temperature value
            int temp = ceil(x);

            // Calculate the fan speed 
            int speed = ceil(min_speed + ((temp - min_temp) * (max_speed - min_speed) / (max_temp - min_temp)));

            // Ensure the calculated speed stays within the defined limits
            speed = clamp(speed, min_speed, max_speed); //  If you are using  framework: type: esp-idf
            // ▲▲▲ ▼▼▼ If you get an error when compiling, try inverting the comment on these lines.
            //speed = constrain(speed, min_speed, max_speed); // If you are using  framework: type: arduino

            call.set_value(speed);

            // Publish the new speed value
            call.perform();