Skip to content

calculating price for stored energy in battery and using dynamically for optimization #185

Description

@ohAnd

based on discussion in PR #172

Sorry for the late response. I was a little busy the last few weeks.

This is all still work in progress, but working pretty well so far.
I have a rather complex template sensor to calculate the battery energy price.
For this to work, I have created two sensors calculating the PV and the grid power that flows into the battery.
In addition, I have a sensor with the current energy price (in my case, Tibber) and the total energy stored in the battery, which is provided by my BMS.

My battery is never discharged below ~15%.
I am subtracting this energy amount (ENERGY_FLOOR_KWH) before doing the whole calculation.

template:
  - trigger:
      # Trigger calculation whenever power, price, or battery energy changes
      - platform: state
        entity_id: 
          - sensor.batterie_leistung_netz
          - sensor.batterie_leistung_pv
          - sensor.strompreis
          - sensor.batterie_energie
    sensor:
      - name: "Batterie Speicher Preis"
        unique_id: batterie_speicher_preis
        unit_of_measurement: "€/kWh"
        icon: mdi:currency-eur
        state: >-
          {# --- CONFIGURATION --- #}
          {% set ENERGY_FLOOR_KWH = 6.8 %}
          {% set EFFICIENCY_RATE = 0.88 %}   {# 12% loss expressed as 0.88 efficiency #}
          {% set price_pv = 0.0 %}
          
          {# Calculate loss factor: 1 / 0.88 ≈ 1,136 (accounts for energy lost during charging) #}
          {% set EFFICIENCY_LOSS_FACTOR = 1.0 / EFFICIENCY_RATE %}
          
          {# --- INPUT SENSORS (Safely convert to float or None) --- #}
          {% set current_energy_kwh = states('sensor.batterie_energie') | float(none) %}
          {% set power_grid_w = states('sensor.batterie_leistung_netz') | float(none) %}
          {% set power_pv_w = states('sensor.batterie_leistung_pv') | float(none) %}
          {% set price_grid = states('sensor.strompreis') | float(none) %}
          {% set old_avg_price = this.state | float(0) %}
          
          {# --- FAILSAFE: Check for unavailable sensors --- #}
          {% if current_energy_kwh is none or power_grid_w is none or power_pv_w is none or price_grid is none %}
            {% set final_price = old_avg_price if old_avg_price > 0 else 0.00 %}
          {% else %}
            
            {# Usable energy above the 6.8 kWh floor #}
            {% set circulating_energy_kwh = [current_energy_kwh - ENERGY_FLOOR_KWH, 0] | max %}
            {% set total_power_w = power_grid_w + power_pv_w %}
            {% set final_price = old_avg_price %}
            
            {# Precise time tracking between updates #}
            {% set time_delta_seconds = (now() - this.last_updated).total_seconds() | float(1) %}
            {% set time_delta_hours = time_delta_seconds / 3600 %}
            
            {# --- CHARGING LOGIC --- #}
            {% if total_power_w > 10 and time_delta_seconds > 0.01 %}
              
              {# Calculate added energy in kWh #}
              {% set added_grid_kwh = (power_grid_w / 1000) * time_delta_hours %}
              {% set added_pv_kwh = (power_pv_w / 1000) * time_delta_hours %}
              {% set added_total_kwh = added_grid_kwh + added_pv_kwh %}
              
              {# Apply Efficiency Loss Factor only to the NEWLY added costs #}
              {% set cost_grid_effective = added_grid_kwh * price_grid * EFFICIENCY_LOSS_FACTOR %}
              {% set cost_pv_effective = added_pv_kwh * price_pv * EFFICIENCY_LOSS_FACTOR %}
              {% set cost_added_effective = cost_grid_effective + cost_pv_effective %}
              
              {# Estimate state BEFORE this update for mathematical consistency #}
              {% set prev_circulating_energy = [circulating_energy_kwh - added_total_kwh, 0] | max %}
              
              {# Value of existing inventory #}
              {% set value_old = prev_circulating_energy * old_avg_price %}
              
              {# Weighted average calculation #}
              {% set new_total_value = value_old + cost_added_effective %}
              {% set new_circulating_energy = prev_circulating_energy + added_total_kwh %}
              
              {% if new_circulating_energy > 0.01 %}
                {% set final_price = new_total_value / new_circulating_energy %}
              {% else %}
                {% set final_price = 0.00 %}
              {% endif %}
            
            {% else %}              
              {# --- DISCHARGING / STANDBY --- #}
              {% if circulating_energy_kwh < 0.01 %}
                 {% set final_price = 0.00 %}
              {% else %}
                 {# Hold price during discharge or until new energy is added #}
                 {% set final_price = old_avg_price if old_avg_price > 0 else price_grid %}
              {% endif %}
            {% endif %}
          {% endif %}
          
          {{ final_price | float(0) | round(4) }}

Originally posted by @rockinglama in #172 (comment)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions