Arbitrage Energy prices during the day

Hello,

I live in Germany which has high energy prices currently. I decided to go for a flexible tariff from tibber. Some days you see price differences of 30€ents during the day.

This is a price curve for a day like today.
image

I build a set of automations to ensure the house battery is charged from the grid during cheap hours (below the white band) and discharged during peak hours (above the white band).

This is done only if the there is a high delta in price during the day. This threshold is introduced to ensure we dont loose the benefit bc f the losses between charging and discharging the battery. The required delta can be configured (here 0.08€).

The battery is charged if the current price is in the lower 5% of the spread and discharged only when the current price is above 50% of the current spread.

In winter, when solar yield is low and we see such a price difference, I can save some 2-3€ during the day. It adds up … and it is fun. Why paying high energy prices when you can build some fancy automations ,-).

image

I have tibber integration which gives me current price and forecast.

  - platform: template
    sensors:
      tibber_price_delta:
        friendly_name: "Tibber Price Delta"
        value_template: >
          {% set max_price = ([states.sensor.tibber_prices.attributes.today | map(attribute='total') | list, states.sensor.tibber_prices.attributes.tomorrow | map(attribute='total') | list] | sum(start=[]) | max | float) %}
          {% set min_price = ([states.sensor.tibber_prices.attributes.today | map(attribute='total') | list, states.sensor.tibber_prices.attributes.tomorrow | map(attribute='total') | list] | sum(start=[]) | min | float) %}
          {% set price_delta =  (max_price - min_price) | float %}
          {{ price_delta }}
        unit_of_measurement: €

  - platform: template
    sensors:
      tibber_price_charge_target:
        friendly_name: "Tibber Price Charge Target"
        value_template: >
          {% set max_price = ([states.sensor.tibber_prices.attributes.today | map(attribute='total') | list, states.sensor.tibber_prices.attributes.tomorrow | map(attribute='total') | list] | sum(start=[]) | max | float) %}
          {% set min_price = ([states.sensor.tibber_prices.attributes.today | map(attribute='total') | list, states.sensor.tibber_prices.attributes.tomorrow | map(attribute='total') | list] | sum(start=[]) | min | float) %}
          {% set now_price = states('sensor.tibber_prices') | float %}
          {% set price_weight =  ((1 - (now_price - min_price) / (max_price - min_price)) * 100 ) | int %}
          {% set price_target= (min_price + (states('input_number.evcc_battery_charge_percentile') | float / 100) * (max_price - min_price)) | float %}
          {{ ((price_target * 1000) + 0.999) // 1 / 1000 }}
        unit_of_measurement: €

  - platform: template
    sensors:
      tibber_price_discharge_target:
        friendly_name: "Tibber Price Discharge Target"
        value_template: >
          {% set max_price = ([states.sensor.tibber_prices.attributes.today | map(attribute='total') | list, states.sensor.tibber_prices.attributes.tomorrow | map(attribute='total') | list] | sum(start=[]) | max | float) %}
          {% set min_price = ([states.sensor.tibber_prices.attributes.today | map(attribute='total') | list, states.sensor.tibber_prices.attributes.tomorrow | map(attribute='total') | list] | sum(start=[]) | min | float) %}
          {% set now_price = states('sensor.tibber_prices') | float %}
          {% set price_weight =  ((1 - (now_price - min_price) / (max_price - min_price)) * 100 ) | int %}
          {% set price_target= (min_price + (states('input_number.evcc_battery_discharge_percentile') | float / 100) * (max_price - min_price)) | float %}
          {{ ((price_target * 1000) + 0.999) // 1 / 1000 }}
        unit_of_measurement: €

I steer my battery charging and discharging or blocking the discharging via EVCC.

In EVCC I introduced a dummy loadpoint in evcc.yaml.

chargers:
- name: dummy
  status: C
  type: template
  template: demo-charger
  enabled: true

loadpoints:
- title: Dummy
  charger: dummy
  mode: off

With this i can easily set the price points where the house battery should be charged. If the dummy loadpoint is charging, EVCC prevents the battery from being discharged. Be sure you configure EVCC properly. Be sure you have experimental mode set to on in EVCC so you can find the required options.



- alias: Sync EVCC Battery Charge Limit with Tibber Price Target
  description: Set the EVCC battery grid charge limit to the value of the Tibber price target whenever it changes.
  trigger:
    - trigger: state
      entity_id: sensor.tibber_price_charge_target
    - trigger: homeassistant
      event: start
  condition:
    - condition: template
      value_template: >
        {{ (states('sensor.tibber_price_delta') | float) > ((states('input_number.evcc_battery_charge_delta')) | float) }}
  action:
    - delay: "00:00:30"
    - action: number.set_value
      target:
        entity_id: number.evcc_battery_grid_charge_limit
      data:
        value: "{{ states('sensor.tibber_price_charge_target') | float }}"
  mode: single

And the following automation prevents the house battery from being discharged during lower prices and restricts the discharging to hours with high pricing.

- alias: "EVCC Battery Discharge Control"
  initial_state: on
  description: >-
    Enables or disables the battery discharge control based on Tibber price index
    and battery energy percentage by setting `select.evcc_dummy_mode`.
  id: ec99e344-2ff0-2405-9570-54784d61d3ea
  mode: queued
  triggers:
    - trigger: homeassistant
      event: start

    - trigger: event
      event_type: automation_reloaded

    - trigger: state
      entity_id:
        - sensor.tibber_price_index
        - sensor.tibber_price_discharge_target

  variables:
    anchors:
      - &discharge_control
        target:
          entity_id: select.evcc_dummy_mode

      - &dont_discharge
        <<: *discharge_control
        alias: "Disable battery discharge"
        data:
          option: "now"
        action: select.select_option

      - &allow_discharge
        <<: *discharge_control
        alias: "Enable battery discharge"
        data:
          option: "off"
        action: select.select_option

  action:
    - choose:
        - alias: "Enable discharge when Tibber price is high"
          conditions:
            - alias: "Tibber Price Below Dynamic Threshold"
              condition: template
              value_template: >
                {{ states('sensor.tibber_prices') | float > (states('sensor.tibber_price_discharge_target') | float ) }}
          sequence:
            - *allow_discharge
      default:
        - *dont_discharge

EVCC provides a switch to turn on/off discharging, but I had troubles using it, so I went the route with the dummy loadpoint.

As long as your house battery is supported by EVCC you can do the same.

Important to know is that EVCC allows for a modbus proxy. Modbus has issues with multiple clients requesting informations from the same modbus device. The proxy is channeling the requests from multiple devices (here EVCC and HA) to my inverter.

Regards
Ralf

1 Like

Changed the calculation for the thresholds to charge/discharge from percentage to N cheapest/most expensive hours. Feels more natural to me and is more robust on different price curves during the day.

  - platform: template
    sensors:
      tibber_price_charge_target:
        friendly_name: "Tibber Cheap Hours Threshold"
        unit_of_measurement: "€/kWh"
        value_template: >
          {% set prices_today = states.sensor.tibber_prices.attributes.today | map(attribute='total') | list %}
          {% set prices_tomorrow = states.sensor.tibber_prices.attributes.tomorrow | map(attribute='total') | list %}
          {% set all_prices = (prices_today + prices_tomorrow) | sort %}
          {% set n = states('input_number.evcc_charge_n') | int %}          
          {% if all_prices | length >= n %}
            {% set price_target = all_prices[n-1] %}
          {% else %}
            {% set price_target = all_prices[-1] if all_prices | length > 0 else 0 %}
          {% endif %}
          {{ ((price_target * 1000) + 0.999) // 1 / 1000 }}
  - platform: template
    sensors:
      tibber_price_discharge_target:
        friendly_name: "Tibber Expensive Hour Threshold"
        unit_of_measurement: "€/kWh"
        value_template: >
          {% set prices_today = states.sensor.tibber_prices.attributes.today | map(attribute='total') | list %}
          {% set prices_tomorrow = states.sensor.tibber_prices.attributes.tomorrow | map(attribute='total') | list %}
          {% set all_prices = (prices_today + prices_tomorrow) | sort(reverse=True) %}
          {% set n = states('input_number.evcc_discharge_n') | int %}          
          {% if all_prices | length >= n %}
            {% set price_target = all_prices[n-1] %}
          {% else %}
            {% set price_target = all_prices[-1] if all_prices | length > 0 else 0 %}
          {% endif %}
          {{ (price_target * 1000) // 1 / 1000 }}