Multi-stage thermostat

Hello,

I want to create this in ESPHome and am mentally at the beginning and end of it at the same time.

I would like to have a thermostat that switches a switch template.
The template should compare the current and target temperatures and switch various output relays based on the difference.

Target temperature 43°C = thermostat value
Current temperature 20°C = temperature sensor (1-Wire)

Difference greater than 10°C
switch output relays 1, 2, and 3

Difference less than 5°C
switch output relays 1, 2, and 3

Difference less than 3°C
switch output relay 1

Difference 0°C
switch all relays off

I know it’s possible with Lambda calls, but my horizons are unfortunately far too narrow.

If anyone has something like this, I would be very happy to get it :wink:
or if someone can help me.

Thank you, best regards,
Gerald

Seems like this would help as a starting point, but you’d have to add some extra logic for the stages:

This is exactly the logic at stake.

Afaik Esphome doesn’t have multi-point thermostat.
You can obviously do it by yourself, but without using labdas it’s not so straightforward.

You could do it with:
-switches
-one wire temp sensor
-template number for setpoint
-template sensor to for differential value and all the automations

Thanks to AI (ChatGPT) I got my controls, they work and I’m happy!

substitutions:
  device_name: nand11-sauna32
  friendly_name: Sauna32
  device_description: Saunasteuerung II

esphome:
  name: ${device_name}
  comment: ${device_description}
  friendly_name: ${friendly_name}

esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: !secret api_password
    
# Enable over-the-air updates
ota:
  platform: esphome
  password: !secret ota_password

# Web server
web_server:
  port: 80
  auth:
    username: !secret webuser
    password: !secret webpasswort
  
# Sync time with Home Assistant
time:
  - platform: homeassistant
    id: homeassistant_time


wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  domain: !secret domain
  ap:
    ssid: ${device_name}_fallback
    password: !secret ap_password
    ap_timeout: 1min

captive_portal:

# Text sensors with general information
text_sensor:
  - platform: version
    name: "T ${friendly_name} Version"
  - platform: wifi_info
    ip_address:
      name: T ${friendly_name} IP Addresse

  - platform: template
    name: "T Sauna Restzeit"
    id: restzeit_text
    icon: "mdi:timer-sync-outline"

  - platform: template
    name: "T Sauna Heizstufe"
    id: heizstufe_text
    icon: "mdi:information-outline"

###########    
# BME280
# 1-Wire DS1820
# HTU21D  = Saunatemperatur (hinten oben)
#
#GPIO32	Relay #1    Interior
#GPIO33	Relay #2    Heizstab 1
#GPIO25	Relay #3    Heizstab 2
#GPIO26	Relay #4    Heizstab 3


#
#		
#GPIO32	Relay #1    Interior	
#GPIO33	Relay #2    Heizstab 1	
#GPIO25	Relay #3    Heizstab 2	
#GPIO26	Relay #4    Heizstab 3	
#		
# 1Wire	GPIO13	
# I2C		
# SDA	GPIO14	
# SDL	GPIO12	
#		
# Eingänge		
# Taster 1	GPIO16	interner PullUp     Saunaofen 
# Taster 2 	GPIO17	interner PullUp     Interior
# Statusgeber GPIO18		
########################



##i2C - Bus
i2c:
  sda: GPIO14
  scl: GPIO12
  scan: true

# --- 1-Wire Temperatursensor ---
one_wire:
  - platform: gpio
    pin: GPIO13


## I2C Sensor -  BME280 (Raumtemperatur / Feuchte)

sensor:
  - platform: bme280_i2c
    temperature:
      name: "S Temperatur (Keller)"
      id: bme280_t
    pressure:
      name: "S Luftdruck (Keller)"
      id: bme280_pressure
    humidity:
      name: "S Luftfeuchtigkeit (Keller)"
      id: bme280_luftfeuchte
    address: 0x76
    update_interval: 15s

## I2C Sensor -  HTU21d (Temperatur / Feuchte - Sauna hinten oben)
  - platform: htu21d
    temperature:
      name: "S Saunatemperatur"
      id: htu

    ## Abschaltung wenn 105°C überschritten ist
      on_value_range:
        - above: 105.0
          then:
            - switch.turn_off: relais_stufe1
            - switch.turn_off: relais_stufe2    
            - switch.turn_off: relais_stufe3
            - switch.turn_off: sauna_switch
            - binary_sensor.template.publish:
                id: sauna_overtemp
                state: ON


    humidity:
      name: "S Feuchtigkeit"
    update_interval: 15s

  - platform: sht3xd
    temperature:
      name: "S Sauna SHT"
      id: sauna_sht
    humidity:
      name: "S Sauna SHT F"
    address: 0x44
    update_interval: 30s



  - platform: dallas_temp
 #  address: 0xXXXXXXXXXXXXXXX  # hier deine Sensor-Adresse eintragen
    name: "S Sauna Temperatur"
    id: ist_temp
    update_interval: 1s
    ## Sichereitsabschaltung wenn 120°C vorne überschritten ist
    on_value_range:
      - above: 120.0
        then:
          - switch.turn_off: relais_stufe1
          - switch.turn_off: relais_stufe2    
          - switch.turn_off: relais_stufe3
          - switch.turn_off: sauna_switch
          - binary_sensor.template.publish:
              id: sauna_overtemp
              state: ON


  - platform: wifi_signal
    name: "T WiFi Signal Sensor"
    update_interval: 30s




# --- Eingabefeld in Home Assistant (Solltemp & Timerzeit) ---
number:
  - platform: template
    name: "E Saunatemperatur"
    id: soll_temp
    icon: "mdi:temperature-celsius"
    min_value: 25
    max_value: 105
    step: 1
    unit_of_measurement: "°C"
    optimistic: true
    initial_value: 60 

  - platform: template
    name: "E Sauna Laufzeit (Minuten)"
    id: sauna_timer_minutes
    icon: "mdi:timer-play-outline"
    min_value: 1
    max_value: 240
    step: 1
    unit_of_measurement: "min"
    optimistic: true
    initial_value: 150   # ⏱️ Startwert = 150 Minuten
    on_value:
      then:
        - lambda: |-
            if (id(thermostat_aktiv)) {
              int neue_laufzeit = id(sauna_timer_minutes).state * 60;
              int differenz = neue_laufzeit - id(timer_restzeit);
              id(timer_restzeit) += differenz;
              ESP_LOGI("main", "Laufzeit angepasst – Restzeit jetzt: %d Sekunden", id(timer_restzeit));
            }


button:
  - platform: restart
    name: "T Sauna Reboot"
    id: sauna_reboot
    icon: "mdi:power-cycle"

  - platform: template
    name: "T Sauna Start/Stopp"
    id: sauna_toggle
    icon: "mdi:fire"
    on_press:
      - lambda: |-
          id(thermostat_aktiv) = !id(thermostat_aktiv);
          if (id(thermostat_aktiv)) {
            id(timer_restzeit) = id(sauna_timer_minutes).state * 60;
            ESP_LOGI("main", "Thermostat über Taster/Software gestartet");
          } else {
            id(heizung_aktiv) = false;
            id(relais_stufe1).turn_off();
            id(relais_stufe2).turn_off();
            id(relais_stufe3).turn_off();
            ESP_LOGI("main", "Thermostat über Taster/Software gestoppt");
          }

  - platform: template
    name: "T Interior"
    id: interior_toggle
    icon: "mdi:lightbulb-on-outline"
    on_press:
      - switch.toggle: interior


# --- Relais-Ausgänge ---
switch:
  - platform: gpio
    name: "R1 - Interior"
    id: interior
    icon: "mdi:music-note-bluetooth"
    inverted: false
    restore_mode: always_off  
    pin: GPIO32

#### Heizstäbe einzeln ####
  - platform: gpio
    id: relais_stufe1
    name: "Heizstab 1"
    pin: GPIO33
    icon: "mdi:radiator"
    inverted: false
    restore_mode: always_off

  - platform: gpio
    id: relais_stufe2
    name: "Heizstab 2"
    pin: GPIO25
    icon: "mdi:radiator"
    inverted: false
    restore_mode: always_off

  - platform: gpio
    id: relais_stufe3
    name: "Heizstab 3"
    pin: GPIO26
    icon: "mdi:radiator"
    inverted: false
    restore_mode: always_off


# --- Software-Taster (Home Assistant) ---
  - platform: template
    name: "Sauna Thermostat"
    id: sauna_switch
    icon: "mdi:fire"
    optimistic: true
    turn_on_action:
      - lambda: |-
          id(thermostat_aktiv) = true;
          id(timer_restzeit) = id(sauna_timer_minutes).state * 60;
          ESP_LOGI("main", "Thermostat per Software eingeschaltet");
    turn_off_action:
      - lambda: |-
          id(thermostat_aktiv) = false;
          id(relais_stufe1).turn_off();
          id(relais_stufe2).turn_off();
          id(relais_stufe3).turn_off();
          ESP_LOGI("main", "Thermostat per Software ausgeschaltet");


# --- Taster zum Ein/Aus-Schalten ---
binary_sensor:
  - platform: gpio
    pin:
      number: GPIO16
      mode: INPUT_PULLUP
      inverted: true
    name: "Taster Sauna"
    id: sauna_taster
    on_press:
      then:
        - lambda: |-
            id(thermostat_aktiv) = !id(thermostat_aktiv);
            if (id(thermostat_aktiv)) {
              ESP_LOGI("main", "Thermostat eingeschaltet");
              id(timer_restzeit) = id(sauna_timer_minutes).state * 60;
            } else {
              ESP_LOGI("main", "Thermostat ausgeschaltet");
              id(relais_stufe1).turn_off();
              id(relais_stufe2).turn_off();
              id(relais_stufe3).turn_off();
            }

  - platform: gpio
    name: "Taster - Interior"
    pin:
      number: GPIO17
      inverted: true
      mode:
        input: true
        pullup: true
    on_press:
      then:
       - switch.toggle: interior



  - platform: template
    name: "T Sauna Übertemperatur"
    id: sauna_overtemp
    device_class: safety



# --- Hilfsvariablen ---
globals:
  - id: thermostat_aktiv
    type: bool
    restore_value: no
    initial_value: 'false'

  - id: timer_restzeit
    type: int
    restore_value: no
    initial_value: '0'

  - id: heizung_aktiv
    type: bool
    restore_value: no
    initial_value: 'false'


# --- Regel-Logik ---
interval:
  - interval: 1s
    then:
      - if:
          condition:
            lambda: 'return id(thermostat_aktiv);'
          then:
            - lambda: |-
                float ist = id(ist_temp).state;
                float soll = id(soll_temp).state;
                float diff = soll - ist;

                // --- Hysterese ---
                if (id(heizung_aktiv)) {
                  if (ist >= soll + 0.1) {
                    id(heizung_aktiv) = false;
                    id(relais_stufe1).turn_off();
                    id(relais_stufe2).turn_off();
                    id(relais_stufe3).turn_off();
                    id(heizstufe_text).publish_state("Zieltemperatur");
                    ESP_LOGI("main", "Zieltemperatur");
                  }
                } else {
                  if (ist <= soll - 1.0) {
                    id(heizung_aktiv) = true;
                    ESP_LOGI("main", "Heizung wieder aktiviert (unter Soll -1°C)");
                  }
                }

                // --- Stufenlogik nur wenn Heizung aktiv ---
                if (id(heizung_aktiv)) {
                  if (diff > 20) {
                    id(relais_stufe1).turn_on();
                    id(relais_stufe2).turn_on();
                    id(relais_stufe3).turn_on();
                    id(heizstufe_text).publish_state("Stufe 3");
                  } else if (diff > 10) {
                    id(relais_stufe1).turn_on();
                    id(relais_stufe2).turn_on();
                    id(relais_stufe3).turn_off();
                    id(heizstufe_text).publish_state("Stufe 2");
                  } else if (diff > 5) {
                    id(relais_stufe1).turn_on();
                    id(relais_stufe2).turn_off();
                    id(relais_stufe3).turn_off();
                    id(heizstufe_text).publish_state("Stufe 1");
                  } else {
                    // nahe Solltemperatur -> nur Stufe 1 bleibt an
                    id(relais_stufe1).turn_on();
                    id(relais_stufe2).turn_off();
                    id(relais_stufe3).turn_off();
                    id(heizstufe_text).publish_state("Stufe 1 (Feinregelung)");
                  }
                }

                // --- Timer ---
                if (id(timer_restzeit) > 0) {
                  id(timer_restzeit) -= 1;
                }

                // --- Wenn Zeit abgelaufen ist ---
                if (id(timer_restzeit) <= 0 && id(thermostat_aktiv)) {
                  id(timer_restzeit) = 0;
                  id(thermostat_aktiv) = false;
                  id(heizung_aktiv) = false;
                  id(relais_stufe1).turn_off();
                  id(relais_stufe2).turn_off();
                  id(relais_stufe3).turn_off();
                  id(sauna_switch).turn_off();
                  id(heizstufe_text).publish_state("Aus (Timer abgelaufen)");
                  id(restzeit_text).publish_state("0 sec");
                  ESP_LOGI("main", "Sauna-Laufzeit abgelaufen – alles aus");
                }

                // --- Restzeit anzeigen ---
                char buffer[32];
                if (id(timer_restzeit) > 59) {
                  sprintf(buffer, "%d min", id(timer_restzeit) / 60);
                } else {
                  sprintf(buffer, "%d sec", id(timer_restzeit));
                }
                id(restzeit_text).publish_state(buffer);

          else:
            - lambda: |-
                id(relais_stufe1).turn_off();
                id(relais_stufe2).turn_off();
                id(relais_stufe3).turn_off();
                id(heizstufe_text).publish_state("Aus");