esphome / feature-requests

ESPHome Feature Request Tracker
https://esphome.io/
404 stars 26 forks source link

Climate Zones #2153

Open clinta opened 1 year ago

clinta commented 1 year ago

Describe the problem you have/What new integration you would like

I would like to be able to associate multiple climate thermostats together, so that run_time and off_time timers are shared between the thermostats, and deadband settings are ignored when heating or cooling is active on any one of the thermostats.

Please describe your use case for this integration and alternatives you've tried:

When using a multi-zone heating system, it makes sense to have a climate thermostat for each zones. But some settings should apply to the entire heating system, not to the zones. Like min_heating_run_time and min_heating_off_time, these are to protect the heating system and prevent short cycling. But once the heating system is on, because one zone is on, the as long as all zones don't turn off before min_heating_run_time, individual zones do not need to stay on for that duration. Additionally, once a zone is on, heat_deadband can be ignored. Once the system is running, it would be beneficial to ignore the deadband and start heating every zone up to heat_overrun, again to minimize the startup cycles of the heating system and avoid the case of one zone reaching temperature, and shutting off the heater right before another zone passes the deadband and starts calling for heat.

Additional context

nagyrobi commented 1 year ago

I'm in the same boat! I have 7 zones (different rooms). Here's how I try to deal with this now:

A thermostat for each room:

- platform: thermostat
  name: ${zone_1_name} heating
  id: heat_01_term
  sensor: heat_01_temp
  startup_delay: false
  default_preset: heat
  on_boot_restore_from: default_preset
  preset:
    - name: heat
      default_target_temperature_low: ${default_target_temp}
  visual:
    min_temperature: 14
    max_temperature: 25
    temperature_step: 0.1
  min_idle_time: 5min
  min_heating_off_time: 7min
  min_heating_run_time: 7min
  heat_deadband: 0.2 °C
  heat_overrun:  0.2 °C
  heat_action:
    - switch.turn_on: heat_01_sw
  idle_action:
    - switch.turn_off: heat_01_sw

Switch heat_01_sw is a relay commanding one or more TRV valves allowing hot water to flow in the radiators corresponding to that room. Time needed for a TRV to open is about 2-3 minutes. There's a separate relay commanding the central heating boiler, heater_sw:

### Big OR switch between the zones
- platform: template
  entity_category: diagnostic
  name: "Heat demanded"
  icon: mdi:gas-burner
  lambda: |-
      return (
        (id(heat_01_sw).state or
        id(heat_02_sw).state or
        id(heat_03_sw).state or
        id(heat_04_sw).state or
        id(heat_05_sw).state or
        id(heat_06_sw).state or
        id(heat_07_sw).state) or
      );
  on_state:
    - lambda: |-
        if (x) {
          id(heater_sw).publish_state(true);
        } else {
          id(heater_sw).publish_state(false);
          }
        }
### Gas burner debounce+delay, to protect
- platform: template
  id: heater_sw
  filters:
    - delayed_on_off: 20s
  on_state:
    - lambda: |-
        if (x) {
          id(heater_relay).turn_on();
        } else {
          id(heater_relay).turn_off();
        }

Last 8 hours: image Topmost is the heater_relay, below it the rooms. As you see, 3 of the rooms have the heating turned off.

clinta commented 1 year ago

It would be a little messy, but I could add the functionality I want with automatons if the climate.control action could dynamically change the heat_deadband and min_heating_run_time values.

nagyrobi commented 1 year ago

Or if they were templatable?

clinta commented 11 months ago

Thinking about winter once again, and I discovered I may be able to achieve this with lambdas using set_heat_deadband and set_heating_minimum_runtime_in_sec. It'll be complicated, but if I get something working I'll update here.

https://esphome.io/api/classesphome_1_1thermostat_1_1_thermostat_climate

Syka2210 commented 7 months ago

Any luck with resolving the short cycling?

clinta commented 7 months ago

I just finished testing a solution that seems to work well. I set all of the thermostats to have min_heating_run_time: 10min.

Then I created a binary sensor for the boiler being on, which is just a logical OR for each thermostat:

In that binary sensor I have actions that change the minimum heating runtime and deadband for each thermostat. This works because if a thermostat starts heating, it continues to heat for the existing minimum runtime, changing the min runtime after the thermostat is in heating mode does not affect that heat cycle. Other thermostats can turn on or off during that minimum time with no restrictions. This will ensure the boiler stays on for that minimum time. Changing the deadband causes every thermostat to heat up opportunistically because the boiler is already on. When the boiler turns off, everything goes back to normal.

substitutions:
  min_cycle_secs: "600"
  heat_deadband: "0.5"

climate:
  - &thermostat
    name: "Office Thermostat"
    id: thermostat_office
    sensor: office_temperature
    platform: thermostat
    on_boot_restore_from: memory
    startup_delay: true
    visual:
      min_temperature: 50 °F
      max_temperature: 75 °F
      temperature_step: 0.5
    min_idle_time: 0s
    min_heating_off_time: 0s
    min_heating_run_time: ${min_cycle_secs}s
    heat_action: {}
    idle_action: {}
    default_preset: Away
    heat_deadband: ${heat_deadband} °C
    heat_overrun: 0 °C
  - name: "Living Room Thermostat"
    sensor: living_room_temperature
    id: thermostat_living_room
    <<: *thermostat
  - name: "Library Thermostat"
    sensor: library_temperature
    id: thermostat_library
    <<: *thermostat
  - name: "Master Bedroom Thermostat"
    sensor: master_bedroom_temperature
    id: thermostat_master_bedroom
    <<: *thermostat
  - name: "Bedroom 1 Thermostat"
    sensor: bedroom_1_temperature
    id: thermostat_bedroom_1
    <<: *thermostat
  - name: "Bedroom 2 Thermostat"
    sensor: bedroom_2_temperature
    id: thermostat_bedroom_2
    <<: *thermostat
  - name: "Basement Thermostat Test"
    sensor: basement_temperature
    id: thermostat_basement
    <<: *thermostat

binary_sensor:
  - name: "Boiler"
    id: boiler_active
    icon: "mdi:water-boiler"
    platform: template
    device_class: running
    lambda: |-
      if (id(thermostat_office).action ==  CLIMATE_ACTION_HEATING) { return true; }
      if (id(thermostat_living_room).action ==  CLIMATE_ACTION_HEATING) { return true; }
      if (id(thermostat_library).action ==  CLIMATE_ACTION_HEATING) { return true; }
      if (id(thermostat_master_bedroom).action ==  CLIMATE_ACTION_HEATING) { return true; }
      if (id(thermostat_bedroom_1).action ==  CLIMATE_ACTION_HEATING) { return true; }
      if (id(thermostat_bedroom_2).action ==  CLIMATE_ACTION_HEATING) { return true; }
      if (id(thermostat_basement).action ==  CLIMATE_ACTION_HEATING) { return true; }
      return false;
    on_press:
      - lambda: |-
          id(thermostat_office).set_heating_minimum_run_time_in_sec(0);
          id(thermostat_office).set_heat_deadband(0);
          id(thermostat_living_room).set_heating_minimum_run_time_in_sec(0);
          id(thermostat_living_room).set_heat_deadband(0);
          id(thermostat_library).set_heating_minimum_run_time_in_sec(0);
          id(thermostat_library).set_heat_deadband(0);
          id(thermostat_master_bedroom).set_heating_minimum_run_time_in_sec(0);
          id(thermostat_master_bedroom).set_heat_deadband(0);
          id(thermostat_bedroom_1).set_heating_minimum_run_time_in_sec(0);
          id(thermostat_bedroom_1).set_heat_deadband(0);
          id(thermostat_bedroom_2).set_heating_minimum_run_time_in_sec(0);
          id(thermostat_bedroom_2).set_heat_deadband(0);
          id(thermostat_basement).set_heating_minimum_run_time_in_sec(0);
          id(thermostat_basement).set_heat_deadband(0);
    on_release:
      - lambda: |-
          id(thermostat_office).set_heating_minimum_run_time_in_sec(${min_cycle_secs});
          id(thermostat_office).set_heat_deadband(${heat_deadband});
          id(thermostat_living_room).set_heating_minimum_run_time_in_sec(${min_cycle_secs});
          id(thermostat_living_room).set_heat_deadband(${heat_deadband});
          id(thermostat_library).set_heating_minimum_run_time_in_sec(${min_cycle_secs});
          id(thermostat_library).set_heat_deadband(${heat_deadband});
          id(thermostat_master_bedroom).set_heating_minimum_run_time_in_sec(${min_cycle_secs});
          id(thermostat_master_bedroom).set_heat_deadband(${heat_deadband});
          id(thermostat_bedroom_1).set_heating_minimum_run_time_in_sec(${min_cycle_secs});
          id(thermostat_bedroom_1).set_heat_deadband(${heat_deadband});
          id(thermostat_bedroom_2).set_heating_minimum_run_time_in_sec(${min_cycle_secs});
          id(thermostat_bedroom_2).set_heat_deadband(${heat_deadband});
          id(thermostat_basement).set_heating_minimum_run_time_in_sec(${min_cycle_secs});
          id(thermostat_basement).set_heat_deadband(${heat_deadband});