Heating COP calculation

Hi!

I have air-water heatpump for hot water and under floor water heating.

From the heater I can get heat meter values (produced heat) for hot water:

and under floor heating:

I have made template sensor to sum them up:

  - platform: template
    sensors:
      total_heating_energy:
        friendly_name: 'Total Heating Energy'
        value_template: "{{ (states('sensor.nibe_heat_meter_heat_cpr_total_system_2')|float +
                             states('sensor.nibe_heat_meter_hw_cpr_total_system_2')|float)|round(3) }}"
        unit_of_measurement: "kWh"
        availability_template: >-
          {{
            is_number(states('sensor.nibe_heat_meter_heat_cpr_total_system_2')) and
            is_number(states('sensor.nibe_heat_meter_hw_cpr_total_system_2')) 
          }}

with a result:

I laso have Shelly EM3 measuring energy for the heat pump. I use template sensor to sum up three phases:

  - platform: template
    sensors:
      heat_pump_energy:
        friendly_name: 'Heat Pump Energy'
        device_class: energy
        unit_of_measurement: "kWh"
        value_template: "{{ (states('sensor.heat_pump_phase_1_energy')|float +
                             states('sensor.heat_pump_phase_2_energy')|float +
                             states('sensor.heat_pump_phase_3_energy')|float )|round(3) }}"
        availability_template: >-
          {{
            is_number(states('sensor.heat_pump_phase_1_energy')) and
            is_number(states('sensor.heat_pump_phase_2_energy')) and
            is_number(states('sensor.heat_pump_phase_3_energy'))
          }}

With a result:

Now what i want to do is to calculate COP ( Coefficient of performance) which is a calulation of
{\displaystyle {\rm {COP}}={\frac {|Q|}{W}}}
Where : * Q\ is the useful heat supplied or removed by the considered system (machine).

  • {\displaystyle W>0\ } is the net work put into the considered system in one cycle.

Q is Total Heating Energy
W is Heating Pump Energy

So it would be simple just to divide the heating energy with spent energy, but it has to be the same period.

My idea was to have Utility Meter helper for both sensors and divide the utility meter sensors. Only drawback is that when the utility meter resets (day, hour etc.) i get the spike in COP value and the graph will have so called errors.

Also when for whatever reason either of the sensors will be unavailable there will be errors as well.

In the end I would like to compare COP with outdoor temperature and as I have also gas heater on hot standby, I would like to calculate when it will be cheaper to use gas instead of heat pump. In really cold outside temperatures like -20 degrees, heat pump COP would be 1.

Make a statistics sensor that sums Q and a separate one for W over the same timeperiod. Make sure to increase your samples to a large value so the timeperiod is hit. Then make a template sensor dividing the 2.

Thank you for responding.

Make sure to increase your samples to a large value so the timeperiod is hit

What does that mean in laymans terms? Could you help me with a code sample? Os at least which State Characteristic, max_age, sampling_size and precision to use?

The integration uses number of samples or time period for a calculation. If the configured number of samples is smaller than the time period, no additional samples will be added to the calculation. If the number of samples is greater than the time period, the time period is honored and the extra samples are ignored.

max age is the time period.
sampling_size is the number of samples.
precision is the number of sig figs.

state characteristic is whatever calc you want.

So for example if i set the time period 5 minutes and sampling size for example 100 or 1000 it should be enough? And probably ā€œchangeā€ would be the right state characteristics in my case?

Edit: If both statistic sensors have same settings, they will be in sync?

itā€™s number of samples. I.e. how many updates you get over your timespan. If you get 10 updates in 5 minutes, make sure your number of samples is set higher than 10. If you donā€™t know, set it to a large amount.

I would think that

They should be on the same time period if they have the same max_age.

I made two statistics helper sensors:

  - platform: statistics
    name: "Heating Energy COP Helper"
    entity_id: sensor.total_heating_energy
    state_characteristic: change
    max_age:
      minutes: 5
    sampling_size: 100
    precision: 5
  - platform: statistics
    name: "Heat Pump Electric Energy COP Helper"
    entity_id: sensor.heat_pump_energy
    state_characteristic: change
    max_age:
      minutes: 5
    sampling_size: 100
    precision: 5

And COP calcluation template sensor:

  - platform: template
    sensors:
      heating_cop_current:
        friendly_name: 'Current Heating COP'
        value_template: "{{ (states('sensor.heating_energy_cop_helper')|float /
                             states('sensor.heat_pump_electric_energy_cop_helper')|float)|round(2) }}"
        unit_of_measurement: "CoP"
        availability_template: >-
          {{
            is_number(states('sensor.heating_energy_cop_helper')) and
            is_number(states('sensor.heat_pump_electric_energy_cop_helper')) 
          }}

result:



Some moments COP is under 1 which it cannot be and helpers have 0 value which makes divinding impossible.

Iā€™ll let it run for a while and see what it the result, but maybe there is a better state characteristics for this case? Maybe sum_differences_nonnegative which will rule out the negatives?

Or longer max age, so there will be more readings to take account. My hunch is that heat pumo and shelly might not send the data in a same frequency.

Or maybe do 5 minute average sensor for COPā€¦

EDIT:
For some reason Heatinf Energy will go to minus value. This is what the heat pump is sending:

So this approach did not work

Well you gotta see why it went unavailable, and you arenā€™t rejecting W <= 0 values. That all has to be done by you in your template that calculates the COP.

It will go unavailable, when heat is not produced, because statistic sensor does not get any value updates.

So to reject values lower or 0 I have to add this to the template?

  - platform: template
    sensors:
      heating_cop_current:
        friendly_name: 'Current Heating COP'
        value_template: "{{ (states('sensor.heating_energy_cop_helper')|float /
                             states('sensor.heat_pump_electric_energy_cop_helper')|float)|round(2) }}"
        unit_of_measurement: "CoP"
        availability_template: >-
          {{
            is_number(states('sensor.heating_energy_cop_helper')) and
            is_number(states('sensor.heat_pump_electric_energy_cop_helper')) 
          }}

Probably to the abailability template part?

        availability_template: >-
          {{
            is_number(states('sensor.heating_energy_cop_helper')) and
            is_number(states('sensor.heat_pump_electric_energy_cop_helper')) 
          }}

Edit:

It should be like this?

{{ 
  states('sensor.heating_energy_cop_helper')|float(0) > 0
  and
  states('sensor.heat_pump_electric_energy_cop_helper')|float(0) > 0
}}

Iā€™d add the logic to the availability template.

Did that. Now Heat Meter statistic sensor is ā€œunknownā€ status quite frequently. Electricity meter however is showing ok values.


@ThomDietrich , I saw you commented in this topid with a similar issue: Help with statistics sensor: calculate increase in the last 48 hours - #10 by BratislaveBoy

Maybe tou have any ideas what to do?

You donā€™t want to filter out zero. Just less than zero. So use >= instead of >

Sensors from Heat Pump are also showing ok values:


I sum them up:

  - platform: template
    sensors:
      total_heating_energy:
        friendly_name: 'Total Heating Energy'
        value_template: "{{ (states('sensor.nibe_heat_meter_heat_cpr_total_system_2')|float +
                             states('sensor.nibe_heat_meter_hw_cpr_total_system_2')|float)|round(3) }}"
        unit_of_measurement: "kWh"
        availability_template: >-
          {{
            is_number(states('sensor.nibe_heat_meter_heat_cpr_total_system_2')) and
            is_number(states('sensor.nibe_heat_meter_hw_cpr_total_system_2')) 
          }}

And then use statistics sensors from them and heat pump energy meter:

  - platform: statistics
    name: "Heating Energy COP Helper"
    entity_id: sensor.total_heating_energy
    state_characteristic: change
    max_age:
      minutes: 5
    sampling_size: 100
    precision: 5
  - platform: statistics
    name: "Heat Pump Electric Energy COP Helper"
    entity_id: sensor.heat_pump_energy
    state_characteristic: change
    max_age:
      minutes: 5
    sampling_size: 100
    precision: 5

And then divide the result:

  - platform: template
    sensors:
      heating_cop_current:
        friendly_name: 'Current Heating COP'
        value_template: "{{ (states('sensor.heating_energy_cop_helper')|float /
                             states('sensor.heat_pump_electric_energy_cop_helper')|float)|round(2) }}"
        unit_of_measurement: "CoP"
        availability_template: >-
          {{ states('sensor.heating_energy_cop_helper')|float(0) > 0 and 
          states('sensor.heat_pump_electric_energy_cop_helper')|float(0) > 0
          }}

my hunch is that statistic senseor is not properly configured. Either the max time or state characteristics

changed that now

Keep in mind that your denominator canā€™t be zero ever. So that should remain >

You could also put in an if statement that outputs zero when the denominator is zero too.

So i came up with this code:

  - platform: template
    sensors:
      heating_cop_current:
        friendly_name: 'Current Heating COP'
        value_template: "{% set q = states('sensor.heating_energy_cop_helper') |float|round(5) %}
                         {% set w = states('sensor.heat_pump_electric_energy_cop_helper') |float|round(5) %}
                         {% if w <= 0 or q < 0 %}
                          0
                         {% else %}
                         {{ (states('sensor.heating_energy_cop_helper')|float /
                             states('sensor.heat_pump_electric_energy_cop_helper')|float)|round(2) }}
                         {% endif %}"
        unit_of_measurement: "CoP"
        availability_template: >-
          {{
            is_number(states('sensor.nibe_heat_meter_heat_cpr_total_system_2')) and
            is_number(states('sensor.nibe_heat_meter_hw_cpr_total_system_2')) 
          }}

I left the availability_template to ā€œis_numberā€ as the if statement now handles the zero and minus values where needed.

Iā€™m i on the right path?

Edit: Now when i could take 5 minutes (or 1 hour) average and not include 0 values, i guess i would get quite clean results. Maybe make a statistics sensor out of the current COP one?

Seems like it. First, use multiline notation instead of quotes. Second thereā€™s no reason to round mid calculation, that just adds error. Round at the end. Third, use the variables you created. Lastly, itā€™s typically easier to read when you look for ā€˜positive thingsā€™ in your if statement.

        value_template: >
          {% set q = states('sensor.heating_energy_cop_helper') | float %}
          {% set w = states('sensor.heat_pump_electric_energy_cop_helper') | float %}
          {% if q >= 0 and w > 0 %}
            {{ (q / w) | round(2) }}
          {% else %}
            0
          {% endif %}

Thanks, your code is much nicer and I get it what I did differently. Coding is not my everyday thing and thank you very much for helping me.

just one question. What is the difference between > and >- in the notation?

It doesnā€™t matter for HA in template sensors, use either one. It only matters when formatting text, which is removed by template sensors, so the functions do nothing. Thatā€™s why I donā€™t bother to use the -.

1 Like