Reading energy tariff forecast from RESTful

Hello,

I’m trying to get a forecast of energy rates for the next day. They are available on the URL I indicate below, at the latest every day at 18:00. My automation to trigger the reading works, at 18:06 every day.

I think I understand how to read the data, but I don’t know how to present it to Home Assistant. In the general case, I’d like to fill a data structure with these rates to be able to query it later. Ideally, I’d also like to delete data older than a year.

For example, I’d like to be able to look up the 4 cheapest hours of the next night (usually from 1:00 to 5:00) and trigger actions at 1:00 and 5:00 (allowing the water heater to be switched on or off).

Even before writing code, I don’t see how a sensor can present values at times that don’t yet exist.

Thanks in advance for your guidance!

URL:
https://api.tariffs.groupe-e.ch/v1/tariffs/vario_plus?start_timestamp=2025-01-12T00:00:00+01:00&end_timestamp=2025-01-13T00:00:00+01:00

returns:

[
    {
        "start_timestamp": "2025-01-12T00:00:00+01:00",
        "end_timestamp": "2025-01-12T00:15:00+01:00",
        "vario_plus": 25.9,
        "unit": "Rp./kWh"
    },
    {
        "start_timestamp": "2025-01-12T00:15:00+01:00",
        "end_timestamp": "2025-01-12T00:30:00+01:00",
        "vario_plus": 26.21,
        "unit": "Rp./kWh"
    },
[...]
    {
        "start_timestamp": "2025-01-12T23:30:00+01:00",
        "end_timestamp": "2025-01-12T23:45:00+01:00",
        "vario_plus": 26.7,
        "unit": "Rp./kWh"
    },
    {
        "start_timestamp": "2025-01-12T23:45:00+01:00",
        "end_timestamp": "2025-01-13T00:00:00+01:00",
        "vario_plus": 25.64,
        "unit": "Rp./kWh"
    }
]

My 2 cents
This does already exist, e.g. the weather forecast.
Thing is that this is stored in attributes and attributes fafaik do not end up in long term statistics so even if you load them (see below), you will not have these available longer than the recorder set cleanup (default 10d)… which blocks your wish to have them for about 1 year.
Extending the recorder just-for-this seems overkill as it will considerably grow the database since it will not be just-for-this.
Storing it externally (file) and adding with each call is also an option but at some point the attributes will likely grow beyond a length readable in HA

EDIT: maybe you can create other sensors based on this which do get stored in LTS?

command_line:
  - sensor: 
        name: test
        scan_interval: 1500
        command: >
             echo "{\"YOURATTRIBUTE\":" $(
             curl 
             -s 
             'https://api.tariffs.groupe-e.ch/v1/tariffs/vario_plus?start_timestamp=2025-01-12T00:00:00+01:00&end_timestamp=2025-01-13T00:00:00+01:00 '
             ) "}" 
        value_template: > 
            {{ value_json.YOURATTRIBUTE | length }}
        json_attributes:
            - YOURATTRIBUTE

This long topic might help you:

Octopus Agile is a UK tariff that varies half-hourly: there’s discussion on there of ways to find the cheapest n-block times.

As a starting point for your RESTful sensor, this code should return the JSON list sorted by price (must be an easier way but a straight dictsort doesn’t seem to work):

{% set ns = namespace(indexed={},out=[]) %}
{% for i in range(value_json|count) %}
  {% set s = value_json[i]['vario_plus']|string %}
  {% if ns.indexed.get(s,'no') == 'no' %}
    {% set ns.indexed = dict(ns.indexed, **{s:[i]}) %}
  {% else %}
    {% set ns.indexed = dict(ns.indexed, **{s:ns.indexed[s] + [i]}) %}
  {% endif %}
{% endfor %}
{% set d = ns.indexed|dictsort %}
{{ d }}
{% for i in d %}
  {% for j in i[1] %}
    {% set ns.out = ns.out + [value_json[j]] %}
  {% endfor %}
{% endfor %}
{{ ns.out }}

Your cheapest 15-minute block is then on the top of the list.

To find bigger blocks of time, you’d need to combine them together then sort. I did this with AppDaemon here.

Yes, Home Assistant is great at what it does, which is being a state model and an ‘easy-to-use’ automation engine based on events.

What HA is not designed to do is capture, process, store and display lots of user data, particularly arrays.

Welcome to Node-RED (available as an HA add-on) which is a programming language with the ability to easily work with data.

OK, so Node-RED is not that popular, and it is YMTL (Yet More To Learn) but hey, it can be really easy to put something together in just 10-20 minutes…

The http node does the API call and returns a JSON object.
The change node processes it in JSONata

(
    $hours:=5;
    $array:=payload^(vario_plus)[[0..$hours*4-1]]^(start_timestamp);
    $array:=$array#$v.(
        $last:= $v<1 ? false : start_timestamp=$array[$v-1].end_timestamp;
        $next:= end_timestamp=$array[$v+1].start_timestamp;
        $position:= $last ?  $next ? "middle" : "end" : $next ? "start" : "only";
        $~>|$|{"index": $v, "link_last": $last, "link_next": $next, "position": $position}|
        );
    $switching:=$array[position!="middle"].(
        $append(position!="end"   ? {"switch": "ON", "at": start_timestamp},
                position!="start" ? {"switch": "OFF", "at": end_timestamp})
    );
    {"max_price": $max($array.vario_plus),
     "min_price": $min($array.vario_plus),
     "array": $array, "switching": $switching}
)

This sorts by price, takes (5) hours of 15 minute best-price periods, re-sorts by time, identifies contiguous periods so every period is either start/middle/end/singleton, picks out the group starts and ends, and creates an array of switch-on and switch-off times [the cheapest block periods].

With a bit more Node-RED you can store this into context or file, and easily pass the array of prices and/or best periods and/or switching times back to Home Assistant inside the attribute of a sensor, for display, graphing, or further automation.

Here is a binary sensor (for switching automations) and I am passing more data in the attributes, including the array of best-time blocks.

Of course, you may find that your energy company hold this tariff data in one large file, and you can request either the latest 24 hours, or as you are using, with timestamps you can pick just what you want. There is, therefore, no need in this situation to store pricing data, however with Node-RED you can always save data to file/data base as you wish.

Hello @StephaneCherpilloz

Did you found a solution? I’m just starting the same integration (group-e vario) and searching what’s the best way to handle it from home assistant.

Thanks

Hi @vingerha,
It is the first time I am try to use rest integration to get data and it makes me a little bit confuse.

I am able to get the data from the API using this code:

# Define sensors to retrieve tariffs from the Groupe-e API
sensor:
  - platform: rest
    name: "vario_plus_tarif"
    resource: "https://api.tariffs.groupe-e.ch/v1/tariffs/vario_plus"
    method: GET
    params:
      start_timestamp: >-
        {{ (now() + timedelta(days=1)).strftime('%Y-%m-%dT00:00:00+01:00') }}
      end_timestamp: >-
        {{ (now() + timedelta(days=2)).strftime('%Y-%m-%dT00:00:00+01:00') }}
    headers:
      Content-Type: "application/json"
    value_template: "{{ value_json[0].vario_plus }}"
    json_attributes:
      - "start_timestamp"
      - "end_timestamp"
      - "vario_plus"
      - "unit"
    unit_of_measurement: "Rp./kWh"

but it returns only the first value because of

value_template: “{{ value_json[0].vario_plus }}”

has 0. I don’t really understand how to get the whole tomorrow values and/or how to use them.
Sorry for the beginner question but I am stuck and any help would be greatly appreciated :slight_smile:

Thanks
Thanks

I posted the command_line sensor on how to do this, not sure why you stick with the above

Hi @vingerha
Thanks for your quick answer.

In fact, I tried but I’m stucked to manage dynamically the timestamps. If I run the curl command via home assistant command line, it works (I had to install coreutils to have date -d available):

 start_time=$(TZ=Europe/Zurich date -d "today 00:00" +"%Y-%m-%dT%H:%M:%S%:z")
        end_time=$(TZ=Europe/Zurich date -d "now + 1 days 00:00" +"%Y-%m-%dT%H:%M:%S%:z")
        echo "{\"vario_plus_for_tomorrow\":" $(
          curl -s "https://api.tariffs.groupe-e.ch/v1/tariffs/vario_plus?start_timestamp=$start_time&end_timestamp=$end_time"
        ) "}"

but If I run same same command via command_line:

command_line:
  - sensor:
      name: vario_plus_tarif_via_command_line
      scan_interval: 1500
      command: >
        start_time=$(TZ=Europe/Zurich date -d "today 00:00" +"%Y-%m-%dT%H:%M:%S%:z")
        end_time=$(TZ=Europe/Zurich date -d "now + 1 days 00:00" +"%Y-%m-%dT%H:%M:%S%:z")
        echo "{\"vario_plus_for_tomorrow\":" $(
          curl -s "https://api.tariffs.groupe-e.ch/v1/tariffs/vario_plus?start_timestamp=$start_time&end_timestamp=$end_time"
        ) "}"
      value_template: >
        {{ value_json.vario_plus_for_tomorrow | length }}
      json_attributes:
        - vario_plus_for_tomorrow

Unable to parse output as JSON: {“vario_plus_for_tomorrow”: Please provide start_timestamp and end_timestamp parameters

So it looks the start_time and end_time variable are not used :frowning:

That’s why I moved to rest integration but then I am stucked with other trouble :rofl:

What I would like to do is to get all the values for tomorrow (provided at around 6PM, that’s why, for now, I get information of today) and then I would like to use these information to identify the cheapest energy price during night to charge my EV.

Where you install coreutils is likely not where the script runs.
Suggest to start SIMPLE and prove that it works … and not add other complexities like parameters

Paremeters you can construct=template from HA directly in the curl statement between {{ }}

I’ve finally been able to create a working code and I am sharing it here if it can help anybody:

command_line:
  #get vario plus tariffs from now until tomorrow end of day
  #must be scheduled at around 6PM to be sure to have to whole data from tomorrow
  - sensor:
      name: vario_plus_tarif_projection
      scan_interval: 31536000
      command: >
        echo "{\"vario_plus_for_tomorrow\":" $(
        curl -s 'https://api.tariffs.groupe-e.ch/v1/tariffs/vario_plus?start_timestamp={{ (now()).strftime('%Y-%m-%dT%H:00:00%:z') }}&end_timestamp={{ (now() + timedelta(days=2)).strftime('%Y-%m-%dT00:00:00%:z') }}'
        ) "}"
      value_template: >
        {{ value_json.vario_plus_for_tomorrow | length }}
      json_attributes:
        - vario_plus_for_tomorrow
  #get dt_plus tariffs from now until tomorrow end of day
  #must be scheduled at around 6PM to be sure to have to whole data from tomorrow
  - sensor:
      name: dt_plus_tarif_projection
      scan_interval: 31536000
      command: >
        echo "{\"dt_plus_for_tomorrow\":" $(
        curl -s 'https://api.tariffs.groupe-e.ch/v1/tariffs/dt_plus?start_timestamp={{ (now()).strftime('%Y-%m-%dT%H:00:00%:z') }}&end_timestamp={{ (now() + timedelta(days=2)).strftime('%Y-%m-%dT00:00:00%:z') }}'
        ) "}"
      value_template: >
        {{ value_json.dt_plus_for_tomorrow | length }}
      json_attributes:
        - dt_plus_for_tomorrow

#extract the vario plus energy cost from attributes of vario_plus_tarif_projection
sensor:
  - platform: template
    sensors:
      vario_plus_tarif_live:
        friendly_name: "Vario Plus live"
        unit_of_measurement: "Rp./kWh"
        value_template: >
          {% set dt = now() %}
          {% set minutes = (dt.minute + dt.second/60 + dt.microsecond/1000000) / 15 %}
          {% set rounded = (minutes | int) * 15 %}
          {% set rounded_time = dt.replace(minute=0, second=0, microsecond=0) + timedelta(minutes=rounded) %}
          {% for item in state_attr('sensor.vario_plus_tarif_projection', 'vario_plus_for_tomorrow') %}
            {% if item.start_timestamp == rounded_time.strftime('%Y-%m-%dT%H:%M:%S+01:00') %}
              {{ item.vario_plus }}
            {% endif %}
          {% endfor %}
  #extract the dt plus energy cost from attributes of dt_plus_tarif_projection
  - platform: template
    sensors:
      dt_plus_tarif_live:
        friendly_name: "dt Plus live"
        unit_of_measurement: "Rp./kWh"
        value_template: >
          {% set dt = now() %}
          {% set minutes = (dt.minute + dt.second/60 + dt.microsecond/1000000) / 15 %}
          {% set rounded = (minutes | int) * 15 %}
          {% set rounded_time = dt.replace(minute=0, second=0, microsecond=0) + timedelta(minutes=rounded) %}
          {% for item in state_attr('sensor.dt_plus_tarif_projection', 'dt_plus_for_tomorrow') %}
            {% if item.start_timestamp == rounded_time.strftime('%Y-%m-%dT%H:%M:%S+01:00') %}
              {{ item.dt_plus }}
            {% endif %}
          {% endfor %}

Then using apex chart, I am able to compare both tarrifs:

type: custom:apexcharts-card
graph_span: 24h
span:
  offset: "-day"
header:
  show: true
  title: ApexCharts-Card
  show_states: true
  colorize_states: true
series:
  - entity: sensor.vario_plus_tarif_live
  - entity: sensor.dt_plus_tarif_live
    curve: stepline

Next topic is to identify the cheapest 2-3hours block during night and during day and be able to trigger EV charge.

a few improvements still to do but it looks to work:

command_line:
  #get vario plus tariffs from now until tomorrow end of day
  #must be scheduled at around 6PM to be sure to have to whole data from tomorrow
  - sensor:
      name: vario_plus_tarif_projection
      scan_interval: 31536000
      command: >
        echo "{\"vario_plus_for_tomorrow\":" $(
        curl -s 'https://api.tariffs.groupe-e.ch/v1/tariffs/vario_plus?start_timestamp={{ (now()).strftime('%Y-%m-%dT%H:00:00%:z') }}&end_timestamp={{ (now() + timedelta(days=2)).strftime('%Y-%m-%dT00:00:00%:z') }}'
        ) "}"
      value_template: >
        {{ value_json.vario_plus_for_tomorrow | length }}
      json_attributes:
        - vario_plus_for_tomorrow
  #get dt_plus tariffs from now until tomorrow end of day
  #must be scheduled at around 6PM to be sure to have to whole data from tomorrow
  - sensor:
      name: dt_plus_tarif_projection
      scan_interval: 31536000
      command: >
        echo "{\"dt_plus_for_tomorrow\":" $(
        curl -s 'https://api.tariffs.groupe-e.ch/v1/tariffs/dt_plus?start_timestamp={{ (now()).strftime('%Y-%m-%dT%H:00:00%:z') }}&end_timestamp={{ (now() + timedelta(days=2)).strftime('%Y-%m-%dT00:00:00%:z') }}'
        ) "}"
      value_template: >
        {{ value_json.dt_plus_for_tomorrow | length }}
      json_attributes:
        - dt_plus_for_tomorrow

sensor:
  #extract the current vario plus energy cost from attributes of vario_plus_tarif_projection
  - platform: template
    sensors:
      vario_plus_tarif_live:
        friendly_name: "Vario Plus live"
        unit_of_measurement: "Rp./kWh"
        value_template: >
          {%- set dt = now() -%}
          {%- set minutes = (dt.minute + dt.second/60 + dt.microsecond/1000000) / 15 -%}
          {%- set rounded = (minutes | int) * 15 -%}
          {%- set rounded_time = dt.replace(minute=0, second=0, microsecond=0) + timedelta(minutes=rounded) -%}
          {%- set vario_plus_attr = state_attr('sensor.vario_plus_tarif_projection', 'vario_plus_for_tomorrow') -%}
          {%- if vario_plus_attr is not none -%}
            {%- for item in vario_plus_attr -%}
              {%- if item.start_timestamp == rounded_time.strftime('%Y-%m-%dT%H:%M:%S+01:00') -%}
                {{- item.vario_plus -}}
              {%- endif -%}
            {%- endfor -%}
          {%- endif -%}

  #extract the current dt plus energy cost from attributes of dt_plus_tarif_projection
  - platform: template
    sensors:
      dt_plus_tarif_live:
        friendly_name: "dt Plus live"
        unit_of_measurement: "Rp./kWh"
        value_template: >
          {%- set dt = now() -%}
          {%- set minutes = (dt.minute + dt.second/60 + dt.microsecond/1000000) / 15 -%}
          {%- set rounded = (minutes | int) * 15 -%}
          {%- set rounded_time = dt.replace(minute=0, second=0, microsecond=0) + timedelta(minutes=rounded) -%}
          {%- set dt_plus_attr = state_attr('sensor.dt_plus_tarif_projection', 'dt_plus_for_tomorrow') -%}
          {%- if dt_plus_attr is not none -%}
            {%- for item in dt_plus_attr -%}
              {%- if item.start_timestamp == rounded_time.strftime('%Y-%m-%dT%H:%M:%S+01:00') -%}
                {{- item.dt_plus -}}
              {%- endif -%}
            {%- endfor -%}
          {%- endif -%}

  #Identify the time with the cheapest 2hours energy cost during night
  - platform: template
    sensors:
      cheapest_vario_plus_2_hours_night:
        friendly_name: "Cheapest Vario Plus for 2 Hours during night"
        value_template: >
          {%- set vario_plus_values = state_attr('sensor.vario_plus_tarif_projection', 'vario_plus_for_tomorrow') -%}
          {%- set min_vario_plus = namespace(result=1000) -%}
          {%- set min_start_time = namespace(result=None) -%}
          {%- set intervals = 8 -%} 
          {%- if vario_plus_values and vario_plus_values | length > intervals -%}
            {%- for i in range(0, vario_plus_values | length - intervals + 1) -%}
              {%- set item_start = as_datetime(vario_plus_values[i].start_timestamp) -%}
              {%- set item_hour = item_start.hour -%}
              {%- if item_hour >= 22 or item_hour < 6 -%}
                {%- set total_vario_plus = namespace(result=0) -%}
                {%- for j in range(0, intervals) -%}
                  {%- set total_vario_plus.result = total_vario_plus.result + (vario_plus_values[i + j].vario_plus | float) -%}
                {%- endfor -%}
                {%- if min_vario_plus.result == None or total_vario_plus.result < min_vario_plus.result -%}
                  {%- set min_vario_plus.result = total_vario_plus.result -%}
                  {%- set min_start_time.result = vario_plus_values[i].start_timestamp -%}
                {%- endif -%}
              {%- endif -%}  
            {%- endfor -%}
            {{ as_timestamp(min_start_time.result) | timestamp_custom('%H:%M %d.%m.%Y') if min_start_time.result else 'No suitable time found' }}
          {%- else -%}
            No data available
          {%- endif -%}

  #Identify the time with the cheapest 2hours energy cost during day
  - platform: template
    sensors:
      cheapest_vario_plus_2_hours_day:
        friendly_name: "Cheapest Vario Plus for 2 Hours during day"
        value_template: >
          {%- set vario_plus_values = state_attr('sensor.vario_plus_tarif_projection', 'vario_plus_for_tomorrow') -%}
          {%- set min_vario_plus = namespace(result=1000) -%}
          {%- set min_start_time = namespace(result=None) -%}
          {%- set intervals = 8 -%} 
          {%- if vario_plus_values and vario_plus_values | length > intervals -%}
            {%- for i in range(0, vario_plus_values | length - intervals + 1) -%}
              {%- set item_start = as_datetime(vario_plus_values[i].start_timestamp) -%}
              {%- set item_hour = item_start.hour -%}
              {%- if item_hour >= 7 or item_hour < 21 -%}
                {%- set total_vario_plus = namespace(result=0) -%}
                {%- for j in range(0, intervals) -%}
                  {%- set total_vario_plus.result = total_vario_plus.result + (vario_plus_values[i + j].vario_plus | float) -%}
                {%- endfor -%}
                {%- if min_vario_plus.result == None or total_vario_plus.result < min_vario_plus.result -%}
                  {%- set min_vario_plus.result = total_vario_plus.result -%}
                  {%- set min_start_time.result = vario_plus_values[i].start_timestamp -%}
                {%- endif -%}
              {%- endif -%}  
            {%- endfor -%}
            {{ as_timestamp(min_start_time.result) | timestamp_custom('%H:%M %d.%m.%Y') if min_start_time.result else 'No suitable time found' }}
          {%- else -%}
            No data available
          {%- endif -%}

automation:
  #Automate the update of vario plus projection at 18:00
  - alias: "Update Vario Plus Tarif Projection at 18:00"
    triggers:
      - trigger: time
        at: "18:00:00"
    conditions: []
    actions:
      - action: homeassistant.update_entity
        metadata: {}
        data:
          entity_id:
            - sensor.vario_plus_tarif_projection
  #Automate the update of dt plus projection at 18:00
  - alias: "Update dt Plus Tarif Projection at 18:00"
    triggers:
      - trigger: time
        at: "18:00:00"
    conditions: []
    actions:
      - action: homeassistant.update_entity
        metadata: {}
        data:
          entity_id:
            - sensor.dt_plus_tarif_projection
  #Automate the update of dt and vario plus live entity
  - alias: update dt and vario live prive every 15min
    triggers:
      - trigger: time_pattern
        minutes: "0"
      - trigger: time_pattern
        minutes: "15"
      - trigger: time_pattern
        minutes: "30"
      - trigger: time_pattern
        minutes: "45"
    conditions: []
    actions:
      - action: homeassistant.update_entity
        metadata: {}
        data:
          entity_id:
            - sensor.vario_plus_tarif_live
            - sensor.dt_plus_tarif_live
    mode: single

Then using apexchart to display the info:

Display the last 24 hours

type: custom:apexcharts-card
graph_span: 24h
span:
  offset: "-1day"
header:
  show: true
  title: Energy cost history (last 24h)
  show_states: false
  colorize_states: true
series:
  - entity: sensor.vario_plus_tarif_live
  - entity: sensor.dt_plus_tarif_live
    curve: stepline

Display the next 30h projection:

type: custom:apexcharts-card
apex_config:
  legend:
    show: false
graph_span: 30h
span:
  start: minute
header:
  show: true
  title: Energy cost projection
  show_states: false
  colorize_states: true
series:
  - entity: sensor.vario_plus_tarif_projection
    name: vario
    extend_to: now
    data_generator: |
      return entity.attributes.vario_plus_for_tomorrow.map((item) => {
        return [new Date(item.start_timestamp).getTime(), item.vario_plus];
      });
  - entity: sensor.dt_plus_tarif_projection
    name: dt
    extend_to: now
    data_generator: |
      return entity.attributes.dt_plus_for_tomorrow.map((item) => {
        return [new Date(item.start_timestamp).getTime(), item.dt_plus];
      });