My first esphome thermostat project almost finished but need help

I’m working on a thermostat that should, in theory, be simple for controlling an old air conditioner. I don’t know much about electronics and have only basic programming knowledge, so I’ve relied on the work of others and ChatGPT. However, there’s one small issue I haven’t been able to solve.

Thermostat setup:

  • Microcontroller: ESP32
  • Components: Temperature and humidity sensor, two-channel relay, small display, and a physical button to turn it on/off.
  • Air conditioner wiring:

Expected behavior and current status:

:white_check_mark: Pressing the button toggles between cooling mode and off. (Works)
:white_check_mark: When the AC is on, the display should be on; when turned off, the display should be blank. (Works)
:white_check_mark: When turning on the AC, both the compressor and fan should turn on. If the compressor was recently turned off, it should wait a set time before turning back on. (Works)
:white_check_mark: When the target temperature is reached, the compressor should turn off, but the fan should remain on. (Works)
:large_orange_diamond: When turning off the AC, the compressor should turn off immediately, and the fan should turn off shortly after. (Partially works)

Specific issue:

If I turn off the AC using the physical button while the compressor is running (cool_action), everything works correctly.
But if the compressor is already off and only the fan is running (idle_action), the system doesn’t turn off the fan.

I’m sharing the full code below to see if anyone can help me fix this last issue. Thanks in advance!

esphome:
  name: esphome-web-aa565c
  friendly_name: Aire Recepción
  min_version: 2024.11.0
  name_add_mac_suffix: false
  on_boot:
    priority: 600
    then:
      - delay: 5s  # Espera a que el ESP termine de inicializar
      - climate.control:
          id: termostato_area
          mode: "OFF"  # Apaga el termostato tras reinicio
      - switch.turn_off: relay_compresor
      - switch.turn_off: relay_ventilador  # Asegura que el ventilador también se apague

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

# Enable logging
logger:

# Enable Home Assistant API
api:

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

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

substitutions:
  area: "Recepción"

# Configuración del bus I2C
i2c:
  sda: GPIO21
  scl: GPIO22
  scan: true
  id: bus_a

# Sensor de temperatura y humedad interno
sensor:
  - platform: am2320
    address: 0x5C
    temperature:
      name: "Temperatura ${area}"
      id: temperatura_area
      filters:
        - offset: -0.8
    humidity:
      name: "Humedad ${area}"
      id: humedad_area
    update_interval: 180s

  # Sensor de temperatura promedio desde Home Assistant
  - platform: homeassistant
    id: temperatura_promedio_ha
    entity_id: sensor.temperatura_promedio_recepcion
    force_update: true

  # Sensor de temperatura usada 
  - platform: template
    id: temperatura_final
    icon: mdi:thermometer
    name: "Temperatura Final ${area}"
    unit_of_measurement: "°C"  # Agregar esta línea
    device_class: temperature  # Esto también ayuda
    state_class: measurement  # Permite estadísticas en HA
    update_interval: 180s
    lambda: |-
      if (!isnan(id(temperatura_promedio_ha).state)) {
        return id(temperatura_promedio_ha).state;
      } else {
        return id(temperatura_area).state;
      }

font:
  - file: "fonts/Roboto_Condensed-Medium.ttf"
    id: font_large
    size: 50
  - file: "fonts/Roboto_Condensed-Medium.ttf"
    id: font_medium
    size: 22

display:
  - platform: ssd1306_i2c
    model: "SSD1306 128x64"
    address: 0x3C
    id: pantalla
    lambda: |-
      if (id(termostato_area).mode != CLIMATE_MODE_OFF) {
        // Mostrar la temperatura actual
        it.printf(0, 10, id(font_large), "%.0f°", id(temperatura_final).state);
        
        // Mostrar la temperatura objetivo
        it.printf(73, 6, id(font_medium), "%.1f°", id(termostato_area).target_temperature);
        
        // Mostrar el modo de operación
        std::string modo;
        if (id(termostato_area).mode == CLIMATE_MODE_COOL) {
            modo = "FRIO";
        } else if (id(termostato_area).mode == CLIMATE_MODE_FAN_ONLY) {
            modo = "VENT";
        } else {
            modo = "OFF";
        }
        it.print(73, 42, id(font_medium), modo.c_str());
      } else {
        it.fill(COLOR_OFF); // Muestra pantalla en blanco cuando el aire está apagado
      }

# Configuración Termostato

globals:
  - id: ultimo_apagado
    type: int
    restore_value: no
    initial_value: '0'

climate:
  - platform: thermostat
    name: "Aire Acondicionado ${area}"
    id: termostato_area
    visual:
      min_temperature: 18 °C
      max_temperature: 30°C
      temperature_step: 0.5 °C
    sensor: temperatura_final
    min_cooling_off_time: 1s  # Recomendado espera 4 min antes de encender de nuevo el compresor pero se está evaluando en cool_action
    min_cooling_run_time: 180s
    min_idle_time: 60s
    min_fanning_off_time: 0s
    min_fanning_run_time: 0s
    cool_deadband: 0.5
    cool_overrun: 0.0
    cool_action:
      - switch.turn_on: relay_ventilador  # Enciende el ventilador primero
      - lambda: 'id(pantalla).turn_on();'  # Enciende la pantalla junto con el ventilador
      - while:
          condition:
            lambda: 'return (millis() - id(ultimo_apagado)) < 240000;'  # Espera hasta que pasen 4 minutos
          then:
            - delay: 1s  # Revisa la condición cada segundo
      - if:
          condition:
            lambda: 'return id(temperatura_final).state > id(termostato_area).target_temperature;'  
          then:
            - switch.turn_on: relay_compresor  # Enciende el compresor solo si la temperatura es mayor que la deseada
    idle_action:
      - lambda: 'id(ultimo_apagado) = millis();'  # Guarda el tiempo de apagado
      - switch.turn_off: relay_compresor
      - switch.turn_on: relay_ventilador
      - delay: 30s  # Reduce consumo energético
      - if:
          condition:
            lambda: 'return id(termostato_area).mode == CLIMATE_MODE_OFF;'
          then:
            - switch.turn_off: relay_ventilador
    fan_only_action:
      - switch.turn_on: relay_ventilador
      - switch.turn_off: relay_compresor
      - lambda: 'id(pantalla).turn_on();'  # Enciende la pantalla

# Configuración de los relés
switch:
  - platform: gpio
    id: relay_ventilador
    name: "Ventilador ${area}"
    icon: "mdi:fan"
    internal: False
    pin: GPIO13
    restore_mode: RESTORE_DEFAULT_OFF
    inverted: False
  - platform: gpio
    id: relay_compresor
    name: "Compresor ${area}"
    icon: "mdi:hvac"
    internal: False
    pin: GPIO14
    restore_mode: ALWAYS_OFF
    inverted: False
    
# Botón físico para alternar entre "Cool" y "Off"
binary_sensor:
  - platform: gpio
    pin:
      number: GPIO19
      mode: INPUT_PULLUP
      inverted: true
    name: "Botón Aire Acondicionado ${area}"
    on_press:
      then:
        - if:
            condition:
              lambda: 'return id(termostato_area).mode == climate::CLIMATE_MODE_OFF;'
            then:
              - climate.control:
                  id: termostato_area
                  mode: COOL
            else:
              - climate.control:
                  id: termostato_area
                  mode: "OFF"

First step to do when you have misbehavior, observe your log.
When you press button, what you get on your log?

mode: OFF

Parece haber una diferencia en la forma de apagar el ventilador entre el modo automático y el modo de presionar el botón.

El modo automático es así:

y el modo de presionar el botón es así:

Espero que esto te ayude