Rather Advance Logic Automation (Water Tanks and Solar Energy)

Hi there folks,

I have recently migrated my Smart Home from smarthomeNG to Home Assistant. And I really love HA for what it is. Easy and maintainable. All can be done from the UI, which is really nice.

There is just one feature I cannot wrap my head around on how to solve that in HA.

In smarthomeNG you can use a python script to run a logic and trigger events.

For example I did this:

from datetime import datetime, time
now = datetime.now()
now_time = now.time()

TWO = sh.Zentral.Kesseltemperaturen.TrinkwasserOben() # Read one temperature
HWO = sh.Zentral.Kesseltemperaturen.HeizungswasserOben() # Read another temperature
Pumpe = sh.Zentral.Heizung.Pumpe() # Read the pump status
Automatik = sh.Zentral.Heizung.Automatik() # Check if the variable automatik is True

if now_time >= time(9,00) and now_time <= time(20,00):
 Nacht = False;
else:
 Nacht = True;

if Automatik == True and Nacht == False:
 if (TWO < 40 or TWO > 60) and (TWO > HWO+5 or TWO > 70):
  if Pumpe == False:
   sh.Zentral.Heizung.Pumpe(True) # activate pump

 elif (HWO > 50 and TWO > 45) and (TWO < 55 or TWO+5 > HWO):
  if Pumpe == True:
   sh.Zentral.Heizung.Pumpe(False) # deactivate pump
  
 else:
  if Pumpe == True:
   sh.Zentral.Heizung.Pumpe(False) # deactivate pump

else:
 if Nacht == True and Automatik == True:
  if Pumpe == True:
   sh.Zentral.Heizung.Pumpe(False) # deactivate pump

It does compare two temperature readings and a status of a pump and in addition a time frame and then decide whether the pump should be activated or not.

I desperately tried to do that in HA but did not succeed. I tried using the basic python implementation and I tried using templates.

This is my last template approach to try and see if it works:

template:
  - binary_sensor:
    - name: "Heizungsautomatik Entscheidung"
      unique_id: heizungsautomatik_entscheidung
      state: >
        {% set two = states('sensor.28_ffd7b0821603_temperatur') | float(0) %}
        {% set hwo = states('sensor.28_ffbd5b831603_temperatur') | float(0) %}
        {% set pump = is_state('switch.heizung_pumpe', 'on') %}
        
        {% if (two < 40 or two > 60) and (two > hwo + 5 or two > 70) %}
          {{ pump }}
        {% elif (hwo > 50 and two > 45) and (two < 55 or two + 5 > hwo) %}
          {{ not pump }}
        {% else %}
          {{ not pump }}
        {% endif %}

This will either return “on” or “off” if I look at this in a Dashboard for example. But as soon as I turn the pump on ( let’s say it wants me to turn it on) then it says to turn it off. If I would let that binary sensor decide to trigger the pump (this is what I want) then the pump would loop on and off.

Is there any chance I can do such things in HA?

For a better understanding I want to explain why I am doing this.

I have two water tanks, one is meant to be for heating the house and one is meant to be for heating the water for the faucets and shower.

There are 2 primary sources of energy, one is a wood heating and one is solar thermal energy. In summer the solar energy will overheat my one tank. This is why I start the pump to get the hot water into the other tank. And what I want to achieve is to automate this process. Otherwise I would need to manually pump the water every 30 minutes or so.

The logic was meant to monitor the faucet tank and if it gets to hot and is hotter than the other tank it should start the pump. As soon as the temperatures even out it should stop pumping to get the faucet tank hot again. And this in a loop until it is 8pm. From 8pm to 9am (at night) the solar energy will not get the water hot anyways. And the next morning, as soon as the faucet tank will get too hot again the logic is supposed to again pump.

I hope you guys can understand and might be able to help me.

Thanks in advance,
Patrick

You’ve got this clause:

 {% if (two < 40 or two > 60) and (two > hwo + 5 or two > 70) %}
          {{ pump }}

… which is basically “if this is true, don’t do anything”.

Then the other two clauses just invert the action.

So what if we use the inverse of the “don’t do anything” clause as a general condition, then just toggle the current state if the condition passes… something like:

template:
  - trigger:
      - platform: state
        to: ~
        entity_id:
          - sensor.28_ffd7b0821603_temperatur
          - sensor.28_ffbd5b831603_temperatur
          - input_boolean.zentral_heizung_automatik
      - platform: state 
        for: "00:05:00"
        entity_id: switch.heizung_pumpe
        to: ~
      - platform: time
        at: "09:00:00"
      - platform: time
        at: "20:00:30"
        id: "off"
    variables:
      two: "{{ states('sensor.28_ffd7b0821603_temperatur') | float(0) }}"
      hwo: "{{ states('sensor.28_ffbd5b831603_temperatur') | float(0) }}"
      pump: "{{ is_state('switch.heizung_pumpe', 'on') }}"
      automatik: "{{ is_state('input_boolean.zentral_heizung_automatik', 'on') }}"
      times: "{{  9 <= now().hour < 20 }}"
    condition:
      - or:
           - "{{ not ((two < 40 or two > 60) and (two > hwo + 5 or two > 70)) }}"
           - condition: trigger
             id: "off"
    binary_sensor:
      - name: "Heizungsautomatik Entscheidung"
        unique_id: heizungsautomatik_entscheidung
        state: |
          {% set current = this.state if this is defined else pump %}
          {{ 'off' if (trigger.id == 'off' or not automatik or not times) else not current | bool }}

The trigger based on the pump switch state is probably gratuitous, but it might catch some edge cases…

EDIT: Added trigger for Input Boolean

What is the point of the elif when right after you have the catch-all else which provides the exact same output…?

Yeah well,

if you look back at the python code this will get clearer. The template I created was not final I guess and as I did not understand how exactly this works it might make no sense at all.

But if I would need to write out what the logic has to do it would be:

Lets call it Tank1 and Tank2, as well as Pump, Automatic, Night:

If Automatic is on and it’s not at night:

  • If Tank1 is less than 40 or more than 60 and Tank2 has at least 5 less or Tank 1 is over 70 → Then pump if the pump is not active

The second condition is to deactivate the pump when the temperatures evened out.

(HWO > 50 and TWO > 45) and (TWO < 55 or TWO+5 > HWO)

You can see that I did an inner if Pumpe == ?? in each of those statements. So this will make sure that you can reach the temperature condition, but as soons as the pump state is not the one expected nothing happens.

The inner else in my previous logic never hit if I am not mistaken. Only the one at “night” did.

That should be the case. The Pump state was catching edge cases.

Can you elaborate where I would need to put that piece of code to test it and also how I might be able to enable and disable this logic piece as a Dashboard button? I see you are using or not automatik or not times. So if I understand correctly I could use the entity input_boolean.zentral_heizung_automatik as an entity of a button, which will enable the logic or not.

And times is clear to me. The platform state with for, does that mean that the pump is running for 5 minutes before being turned off? This would eradicate my second elif statement which took care of deactivating the pump.

Thanks

@Didgeridrew : I added your code snippet to my configuration.yaml and made sure that I have all entities in place.

I put a switch to switch on the automatic (input_boolean.zentral_heizung_automatik) and can therefore switch it on and off.

I now have a TWO of 60,5 °C and a HWO of 53,3 °C at 14:45. I would expect the automatic to now turn on the pump to even out the temperatures. But it does not work.

If I look at heizungsautomatik_entscheidung at the dev tools → state then it is “unknown”.

How can I debug this?

You can paste the important parts into the Template tool, just make sure to assign all the variables in Jinja… the template editor does not understand YAML.

    #{% set trigger = {"id":0 }%}

    variables:
      two: "{{ states('sensor.28_ffd7b0821603_temperatur') | float(0) }}"
      # {% set two = states('sensor.28_ffd7b0821603_temperatur') | float(0) %}
      hwo: "{{ states('sensor.28_ffbd5b831603_temperatur') | float(0) }}"
      # {% set hwo = states('sensor.28_ffbd5b831603_temperatur') | float(0) %}
      pump: "{{ is_state('switch.heizung_pumpe', 'on') }}"
      # {% set pump = is_state('switch.heizung_pumpe', 'on') %}
      automatik: "{{ is_state('input_boolean.zentral_heizung_automatik', 'on') }}"
      # {% set automatik = is_state('input_boolean.zentral_heizung_automatik', 'on') %}
      times: "{{  9 <= now().hour < 20 }}"
      # {% set times = 9 <= now().hour < 20 %}
    condition:
      - or:
           - "{{ not ((two < 40 or two > 60) and (two > hwo + 5 or two > 70)) }}"
           - condition: trigger
             id: "off"
    binary_sensor:
      - name: "Heizungsautomatik Entscheidung"
        unique_id: heizungsautomatik_entscheidung
        state: |
          {% set current = this.state if this is defined else pump %}
          {{ 'off' if (trigger.id == 'off' or not automatik or not times) else not current | bool }}

This is a really good way of debugging. Nice and thanks for the hint.

If I change ((two < 40 or two > 60) to ((two < 40 or two > 70) it gets True, which I can totally understand. The automatic would stop at 60 and my temps are beyond 60 so it is False. I could increase it to 70 because the second condition triggers anyways when we are above 70.

What I can also see is that the binary_sensor is True no matter what the conditions are above. Why is that?

Also changing the 60 to 70 I would assume the pump should went off. But it does not.

Also heizungsautomatik_entscheidung will be unkown, no matter what:

Is binary_sensor even the correct type? I want to make this template turn on the pump and turn it off. So binary_sensor looks wrong to me? Or do I understand the template wrong?

Because this isn’t complete method of debugging… it’s just a gross check of whether there are any glaring errors in the templating. And since it has no access to the value of the this variable, and the conditions block isn’t actually affecting whether the “state” template is updated, it’s really only a partial check.

A state of unknown most often means there is an issue in the configuration itself. Make sure you do not have multiple template keys in your configuration.yaml file. I don’t remember if it’s automatically added, but it is very common for there to be a line like:

template: !include template.yaml

which means there is a separate file where your template entries should go.

Binary sensor is the correct type, but the template sensor itself performs no actions… it’s just processing the available data.

Once you get the sensor working, you can use it as the trigger for an automation.

triggers:
  - trigger: state
    entity_id: binary_sensor.heizungsautomatik_entscheidung
    to:
      - "on"
      - "off"
conditions: []
actions:
  - action: "switch.turn_{{trigger.to_state.state}}"
    target:
      entity_id: switch.heizung_pumpe

There is only that one template in my configuration.yaml. I have written that template out in the file directly instead of including a template.yaml. So there is only that one key.

I created an automation that will turn the pump on if the binary_sensor is true and off if it is off again. Then I set the state of the binary_sensor in the dev tools and the automation does work. So it is just a matter of not having unknown but to have either true or false.

Also it looks like it does not turn off after 5 minutes for whatever reason.

I would love to provide more useful information to drill down on the error, if only I knew how.

As far as I know the shorthand versions should be accepted in template entities configs, but just in case let’s try using the regular versions in the condition block:

    condition:
      - condition: or
        conditions:
           - condition: template
             value_template: "{{ not ((two < 40 or two > 60) and (two > hwo + 5 or two > 70)) }}"
           - condition: trigger
             id: "off"

Make sure to reload Template Entities in the YAML tool after making changes.

It’s normal for a trigger-based template entity to have an unknown state until one of the triggers fires and the conditions are passed. You should probably add a trigger for your Input boolean so it will force a check when you switch it off or on.

Also, check your logs to see if there are any related errors.

I changed to that code snippet instead. I do not know how to reload the Entities, but I did restart Home Assistant.

The unknown state did not return after setting it to on and off via the dev tools. But unfortunately no matter what or how the logic should trigger, it doesn’t. The template tool showed different results throughout my investigation of the issue but the actual state does not change.

How would I add the trigger for the force check? This might be very helpful I could imagine.

Unfortunately weather is bad too. Not a lot of sun.

Help is very much appreciated.

@Didgeridrew or any other capable.

This is what I have now:

  - trigger:
      - platform: state
        to: null
        entity_id:
          - sensor.28_ffd7b0821603_temperatur
          - sensor.28_ffbd5b831603_temperatur
      - platform: state 
        for: "00:05:00"
        entity_id: switch.heizung_pumpe
        to: null
      - platform: time
        at: "09:00:00"
      - platform: time
        at: "20:00:00"
        id: "off"
    variables:
      two: "{{ states('sensor.28_ffd7b0821603_temperatur') | float(0) }}"
      hwo: "{{ states('sensor.28_ffbd5b831603_temperatur') | float(0) }}"
      pump: "{{ is_state('switch.heizung_pumpe', 'on') }}"
      automatik: "{{ is_state('input_boolean.zentral_heizung_automatik', 'on') }}"
      times: "{{  9 <= now().hour < 20 }}"
    condition:
      - condition: or
        conditions:
           - condition: template
             value_template: "{{ not ((two < 40 or two > 70) and (two > hwo + 5 or two > 70)) }}"
           - condition: trigger
             id: "off" 
    binary_sensor:
      - name: "Heizungsautomatik Entscheidung"
        unique_id: heizungsautomatik_entscheidung
        state: |
          {% set current = this.state if this is defined else pump %}
          {{ 'off' if (trigger.id == 'off' or not automatik or not times) else not current | bool }}

As you can see from the screenshot, Pump is disabled, 40 < TWO < 70, TWO > HWO + 5 and automatic is turned on. This should in theory turn the “Entscheidung” to True, but it doesn’t.

Looking at that screenshot from the dev tools → template tab it should be True, right?

Can anyone explain again what exactly the template is doing? Maybe then I can figure why it does not work. As of now I think I do not understand what the binary_sensor part does. Condition and vars should be clear.

Switching off the Automatik input boolean will result in True becoming “off” in the last line.

image

This is from Settings->Logs. But I think this one is a result from the dev tools not the template.

Not necessarily, the Template tool only renders Jinja templates, but the trigger-based template sensor has an overall YAML structure that affects its output which is not accounted for by the Template tool.

First the trigger must fire, then the conditions must pass, finally the state is updated.


I think we need to wind this back a bit… It might be best to start with a binary sensor that only accounts for the temperatures being in the right ranges for the pump to run.

template:
  - binary_sensor:
      - name: "Tank Temps Pump Range"
        state: | 
          {% set two = states('sensor.28_ffd7b0821603_temperatur') | float(0) %}
          {% set hwo = states('sensor.28_ffbd5b831603_temperatur') | float(0) %}
          {{ (two < 40 or two > 60) and (two > hwo + 5 or two > 70) }}
        delay_on: "00:00:30"
        delay_off: "00:00:30"

Then handle the rest of it in an automation…

triggers:
  - platform: state
    entity_id: 
      - input_boolean.zentral_heizung_automatik
      - binary_sensor.tank_temps_pump_range
    to:
      - "on"
      - "off"
  - platform: state
    entity_id: switch.heizung_pumpe
    to: ~ 
    for: "00:05:00"
  - platform: time
    at: "09:00:00"
    id: "on"
  - platform: time
    at: "20:00:00"
    id: "off"
conditions:
  - or:
      - condition: template
        value_template: "{{ trigger.platform == 'time' }}"
      - condition: time
        after: "09:00:00"
        before: "20:00:00"
actions:
  - variables:
      in_range: "{{ states('binary_sensor.tank_temps_pump_range') }}"
      automatik: "{{ states('input_boolean.zentral_heizung_automatik') }}"
      pump_action: |
        {% if trigger.id == "off" %} off
        {% elif automatik and in_range %} on
        {% else %} off
        {% endif %}
  - action: switch.turn_{{pump_action}}
    target:
      entity_id: switch.heizung_pumpe

By moving it to an automation we’ll have access to debug traces, which should make it easier to figure out any issues. If you don’t want to test it live on the actual pump, you could create an input boolean for the automation to turn off and on, just make sure you include it in the trigger section like the pump switch is included.

Okay thanks for the suggestions. I will integrate the code as suggested. It will take at least until Thursday, as then the sun will return. Then I will have feedback. Thanks for the help. Really appreciated.

So now I have a solution that works for me:

templates.yaml

  - binary_sensor:
      - name: "Tank Temps Pump Range"
        state: | 
          {% set two = states('sensor.28_ffd7b0821603_temperatur') | float(0) %}
          {% set hwo = states('sensor.28_ffbd5b831603_temperatur') | float(0) %}
          {{ (40 < two < 60) and (two > hwo + 5 or two > 70) }}
        delay_on: "00:00:30"
        delay_off: "00:00:30"

automations.yaml

- id: '1743406549000'
  alias: Sommerautomatik
  description: Wälzt im Sommer das heiße Wasser in den Heizungstank um
  triggers:
  - entity_id:
    - input_boolean.zentral_heizung_automatik
    to:
    - 'on'
    - 'off'
    trigger: state
  - entity_id:
    - switch.heizung_pumpe
    to:
    for:
      hours: 0
      minutes: 5
      seconds: 0
    trigger: state
  - trigger: state
    entity_id:
    - sensor.28_ffd7b0821603_temperatur
    attribute: raw_value
  conditions:
  - condition: and
    conditions:
    - condition: time
      after: 09:00:00
      before: '20:00:00'
    - condition: state
      entity_id: input_boolean.zentral_heizung_automatik
      state: 'on'
  actions:
  - variables:
      in_range: '{{ states(''binary_sensor.tank_temps_pump_range'') }}'
  - action: switch.turn_{{in_range}}
    target:
      entity_id: switch.heizung_pumpe

That does exactly what I wanted it to do.

However, I will most suddenly polish it a bit, so I can define the difference between TWO and HWO from the Dashboard and also the high and low end TWO has to be in.

Thanks again @Didgeridrew . Building it as a combination of automation and template and using the logs and traces made it easily understandable to me. This helped a lot with the general understanding of Home Assistant and its innner workings.

1 Like

I am done polishing it. This is how I will test it for this summer now:

As you can see I can now define the lower and upper threshold, as well as the difference needed for the automatic to trigger the pump. This will have an effect on the template as shown in the following template.yaml

  - binary_sensor:
      - name: "Tank Temps Pump Range"
        state: | 
          {% set two = states('sensor.28_ffd7b0821603_temperatur') | float(0) %}
          {% set hwo = states('sensor.28_ffbd5b831603_temperatur') | float(0) %}
          {% set schwelle_two_unten = states('input_number.sommerautomatik_untere_schwelle_two') | float(0) %}
          {% set schwelle_two_oben = states('input_number.sommerautomatik_obere_schwelle_two') | float(0) %}
          {% set differenz = states('input_number.sommerautomatik_differenz_two_hwo') | float(0) %}
          {% set differenz_temp = states('sensor.28_ffbd5b831603_temperatur') | float(0) + states('input_number.sommerautomatik_differenz_two_hwo') | float(0) %}
          {{ (schwelle_two_unten < two < schwelle_two_oben) and (two > differenz_temp or two > 70) }}

The automations-part stays the same.