TheFes / cheapest-energy-hours

Jinja macro to find the cheapest energy prices
GNU General Public License v3.0
41 stars 6 forks source link

Add Spain OMIE and PVPC #144

Closed arrgj closed 1 week ago

arrgj commented 2 weeks ago

Struggling to get your very nifty code working with two integrations which have no usable time_key or value_key: 1. https://github.com/luuuis/hass_omie This integration stores the day's data in the Today Hours attribute, but it has no prefix for Times or Prices. Spent hours trying to figure out how to add a prefix to the data -- turns out to be much harder than expected!

Today hours 
'2024-06-17T00:00:00+02:00': 41.49
'2024-06-17T01:00:00+02:00': 35.01
'2024-06-17T02:00:00+02:00': 35
'2024-06-17T03:00:00+02:00': 35
...

image

and the official HA integration 2. https://www.home-assistant.io/integrations/pvpc_hourly_pricing/ This integration also has no price prefix, and stores Price data in 24 different attributes, e.g:

Price 00h 0.09043
Price 01h 0.08548
Price 02h 0.08722
Price 03h 0.08723
...

image

It works a treat on nordpool - can these Spanish providers be added? Thanks!

TheFes commented 2 weeks ago

I won't add support for those data structures in the macro. But I can write up a template sensor to convert the data to a format the macro can work with.

arrgj commented 1 week ago

Awesome, thanks!

TheFes commented 1 week ago

For omie (replace sensor.omie_spot_price_es everywhere with the sensor enitty_id you want to use)

template:
  - sensor:
      - unique_id: fa408bd9-458d-4104-94a2-8d76a5065f80
        name: Omie prices for macro
        state: >
          {% set sensor = 'sensor.omie_spot_price_es' %}
          {{ states(sensor) if sensor | has_value else 'source sensor not available' }}
        attributes:
          tomorrow_valid: >
            {% set sensor = 'sensor.omie_spot_price_es' %}
            {{ (state_attr(sensor, 'tomorrow_hours') | default({'na': none}, true)).values() | first is not none }}
          raw_today: >
            {% set sensor = 'sensor.omie_spot_price_es' %}
            {% if sensor and sensor | has_value and state_attr(sensor, 'today_hours') is mapping %}
              {% set ns = namespace(today=[]) %}
              {% for k, v in state_attr('sensor.omie_spot_price_es', 'today_hours').items() %}
                {% set ns.today = ns.today + [dict(start=k.isoformat(), price=v)] %}
              {% endfor %}
              {{ ns.today }}
            {% else %}
              []
            {% endif %}
          raw_tomorrow: >
            {% set sensor = 'sensor.omie_spot_price_es' %}
            {% if this.attributes.get('tomorrow_valid', false) %}
              {% set ns = namespace(tomorrow=[]) %}
              {% for k, v in state_attr('sensor.omie_spot_price_es', 'tomorrow_hours').items() %}
                {% set ns.tomorrow = ns.tomorrow + [dict(start=k.isoformat(), price=v)] %}
              {% endfor %}
              {{ ns.tomorrow }}
            {% else %}
              []
            {% endif %}
TheFes commented 1 week ago

for PVPC (replace sensor.esios_pvpc everywhere with the sensor enitty_id you want to use)

template:
  - sensor:
      - unique_id: c3d573fe-ce23-49fe-bd6f-07825b9528ed
        name: PVPC prices for macro
        state: >
          {% set sensor = 'sensor.esios_pvpc' %}
          {{ states(sensor) if sensor | has_value else 'source sensor not available' }}
        attributes:
          tomorrow_valid: >
            {% set sensor = 'sensor.esios_pvpc' %}
            {{ states[sensor].attributes.keys() | select('search', '^price_next_day_.*h$') | list | count > 0 }}
          raw_today: >
            {% set sensor = 'sensor.esios_pvpc' %}
            {% set today = states[sensor].attributes.keys() | select('search', '^price_.*h$') | reject('search', 'next') | sort | list %}
            {% set ns = namespace(today=[]) %}
            {% for attr in today %}
              {% set h = attr.split('_')[-1] | replace('h', '') | int %}
              {% set ns.today = ns.today + [dict(start=(today_at()+timedelta(hours=h)).isoformat(), price=state_attr(sensor, attr))] %}
            {% endfor %}
            {{ ns.today }}
          raw_tomorrow: >
            {% set sensor = 'sensor.esios_pvpc' %}
            {% set tomorrow = states[sensor].attributes.keys() | select('search', '^price_next_day_.*h$') | sort | list %}
            {% set ns = namespace(tomorrow=[]) %}
            {% for attr in tomorrow %}
              {% set h = attr.split('_')[-1] | replace('h', '') | int %}
              {% set ns.tomorrow = ns.tomorrow + [dict(start=(today_at()+timedelta(hours=h)).isoformat(), price=state_attr(sensor, attr))] %}
            {% endfor %}
            {{ ns.tomorrow }}
TheFes commented 1 week ago

Can you test this? If it works I will put them in the documentation as well

arrgj commented 1 week ago

It works beautifully! 😄

Also tweaked it to run on the output of this Octopus energy calculator which includes tax calculations: https://github.com/luuuis/hass_omie/issues/65. Seriously considering signing up for a flexi tariff now, very cool!

  - sensor:
    - unique_id: fa408bd9-458d-4104-94a2-8d76a5065f81
      name: Octopus Flexi prices for macro
      state: >
        {% set sensor = 'sensor.octopus_flexi_es' %}
        {{ states(sensor) if sensor | has_value else 'source sensor not available' }}
      attributes:
        tomorrow_valid: >
          {% set sensor = 'sensor.octopus_flexi_es' %}
          {{ (state_attr(sensor, 'tomorrow_hours') | default({'na': none}, true)).values() | first is not none }}
        raw_today: >
          {% set sensor = 'sensor.octopus_flexi_es' %}
          {% if sensor and sensor | has_value and state_attr(sensor, 'today_hours') is mapping %}
            {% set ns = namespace(today=[]) %}
            {% for h, v in state_attr('sensor.octopus_flexi_es', 'today_hours').items() %}
              {% set ns.today = ns.today + [dict(start=h, price=v)] %}
            {% endfor %}
            {{ ns.today }}
          {% else %}
            []
          {% endif %}
        raw_tomorrow: >
          {% set sensor = 'sensor.octopus_flexi_es' %}
          {% if this.attributes.get('tomorrow_valid', false) %}
            {% set ns = namespace(tomorrow=[]) %}
            {% for h, v in state_attr('sensor.octopus_flexi_es', 'tomorrow_hours').items() %}
              {% set ns.tomorrow = ns.tomorrow + [dict(start=h, price=v)] %}
            {% endfor %}
            {{ ns.tomorrow }}
          {% else %}
            []
          {% endif %}

Test sensor:

  - sensor:
    - unique_id: f8853830-3
      name: Cheapest Energy 8h Octopus
      device_class: timestamp
      state: >
        {%- set sensor = 'sensor.octopus_flexi_prices_for_macro' -%}
        {% from "cheapest_energy_hours.jinja" import cheapest_energy_hours %}
        {{ cheapest_energy_hours(sensor=sensor, hours=8, start='00:00', end='00:00', look_ahead=true, include_tomorrow=true) }}
      state: >
        2024-06-21T11:00:00+02:00

Swapping in the PVPC sensor produces an error:

      state: >
        1 error: 2 datapoints (0.5 hours) in selection, where 32 (8.0 hours) are expected

        0.17396

Tried removing the end parameter, same error. Haven't found other ways to break it yet

TheFes commented 1 week ago

Looking at the time of posting, it seems like the attribute for raw_tomorrow was empty, so there was only 0.5 hrs or data available.

Let me check the attribute for tomorrow later today (when the prices are available)

TheFes commented 1 week ago

I just checked an for me it works. I've created the same setup as you have, and the octopus sensor has the data for tomorrow. Using something like this gives me 7:00 tomorrow morning as best price

{%- set sensor = 'sensor.octopus_flexi_prices_for_macro' -%}
{% from 'cheapest_energy_hours.jinja' import cheapest_energy_hours %}
{{ cheapest_energy_hours(sensor=sensor, hours=12, start='00:00', end='00:00', look_ahead=true, include_tomorrow=true) }}
TheFes commented 1 week ago

oh wait, the pvpc sensor is giving the errors. I also don't see tomorrows prices in that sensor. I know it worked last Wednesday, when I added the integration myself. I made a note then on how the data is formatted for tomorrows prices. However, I don't see tomorrows prices in the sensor now, even after reloading the integration. Seems there is something wrong in the integration or at pvpc side regarding tomorrows prices.