Custom Component: ENTSO-e Day Ahead Energy Prices

Are there seperate sensors for “today” and “tomorrow” ? Id like to make a flow that uses a offset so making a device turn on when the price is cheapest within (x) hours. Where x can be from 20.00 till the next day 08.00 for example. Not sure how to format it yet though, you got any pointers for me?

3 Likes

Thanks for this, great help and resource! Perhaps @JaccoR could look into integrating these custom scripts to the component itself for “out of the box” experience :wink:

This would be very helpful, I am not sure if it is only me or everyone, but the example chart card dies after midnight and it stays blank until noon. As Entso-e delivers prices for tomorrow, I am bit baffled where did the data from yesterday (today’s prices) disappear.

Or perhaps I am just lost how the component and Entso-e API works.

you probably only include the “tomorrow” prices? After midnight tomorrow becomes today and the new tomorrow is only available as from 2 or 3 pm. This may be because you refer to a sensor which no longer contains all attributes (this was recently changed in the integration) - below my code which works seamlessly

series:
  - entity: sensor.average_electricity_price_today_2
    show:
      legend_value: false
    stroke_width: 2
    float_precision: 3
    type: column
    opacity: 0.3
    color: '#1007f0'
    data_generator: |
      return entity.attributes.prices_today.map((record, index) => {
        return [record.time, record.price];
      });
  - entity: sensor.average_electricity_price_today_2
    show:
      legend_value: false
    stroke_width: 2
    float_precision: 3
    type: column
    opacity: 0.3
    color: '#07bef0'
    data_generator: |
      return entity.attributes.prices_tomorrow.map((record, index) => {
        return [record.time, record.price];
      });
1 Like

Thanks, I’ll test this and report back :slight_smile:

Edit: Worked great! Will update open issue on GH accordingly :+1:

With the help of several examples already shared on this forum my attempt at a sensor which provides a True / False output on the upcoming 3 cheapest hours; this will be rolling and looks at all available data (today and tomorrow) as from the current hour. Purpose is to automate charging during the cheapest hours; I added state attributes with the time of the cheapest hours. In my automation I assess the time needed to charge the car (Target SoC - Actual SoC times total battery size divided by charging speed). If >2 I charge during all 3 hours, >1 during the 2 cheapest hours etc.

Intention is to make this dynamic depending on the estimated charge time required (eg if 4 hours are needed then add a 4th hour) and prices (top up to 90 or 100% if tariffs are ultra low. Tried to implement the former with a for loop but failed so far. Presumably there are more efficiencies to be added; any suggestions are much appreciated.

Not tested in real life, will do so during the coming night and adjust where needed.

  cheapest_3_hours:
    friendly_name: cheapest_3hours
    value_template: >-
      {% set t = now().hour %}
      {%if (state_attr('sensor.average_electricity_price_today', 'prices_tomorrow'))!=[]%}
        {% set l = state_attr('sensor.average_electricity_price_today', 'prices')[t:60]|sort(attribute='price')%}
      {%else%}
        {% set l = state_attr('sensor.average_electricity_price_today', 'prices_today')[t:60]|sort(attribute='price')%}
      {%endif%}
      {% set t = now() %}      
      {{ (t >= as_datetime(l[0].time) and t <= as_datetime(l[0].time) + timedelta(hours = 1))
        or (t >= as_datetime(l[1].time) and t <= as_datetime(l[1].time) + timedelta(hours = 1))
        or (t >= as_datetime(l[2].time) and t <= as_datetime(l[2].time) + timedelta(hours = 1)) }}
    attribute_templates:
      hour1: >
        {% set t = now().hour %}
        {%if (state_attr('sensor.average_electricity_price_today', 'prices_tomorrow'))!=[]%}
          {% set l = state_attr('sensor.average_electricity_price_today', 'prices')[t:60]|sort(attribute='price')%}
        {%else%}
          {% set l = state_attr('sensor.average_electricity_price_today', 'prices_today')[t:60]|sort(attribute='price')%}
        {%endif%}
        {{ (as_datetime(l[0].time)) }}
      hour2: >
        {% set t = now().hour %}
        {%if (state_attr('sensor.average_electricity_price_today', 'prices_tomorrow'))!=[]%}
          {% set l = state_attr('sensor.average_electricity_price_today', 'prices')[t:60]|sort(attribute='price')%}
        {%else%}
          {% set l = state_attr('sensor.average_electricity_price_today', 'prices_today')[t:60]|sort(attribute='price')%}
        {%endif%}
        {{ (as_datetime(l[1].time)) }}
      hour3: >
        {% set t = now().hour %}
        {%if (state_attr('sensor.average_electricity_price_today', 'prices_tomorrow'))!=[]%}
          {% set l = state_attr('sensor.average_electricity_price_today', 'prices')[t:60]|sort(attribute='price')%}
        {%else%}
          {% set l = state_attr('sensor.average_electricity_price_today', 'prices_today')[t:60]|sort(attribute='price')%}
        {%endif%}
        {{ (as_datetime(l[2].time)) }}
1 Like

My dynamic prices chart is starting to look neat using apexcarts:

image

Price today & tomorrow on top of each other. Showing both the grid and injection prices using two entities.

I’m still looking for a way to make the header numbers a bit smaller, any tips are welcome!

Code:

type: custom:apexcharts-card
graph_span: 24h
span:
  start: day
all_series_config:
  stroke_width: 1
  show:
    in_brush: true
now:
  show: true
  label: Nu
header:
  show: true
  title: Electriciteitsprijzen - afname (€/kwh)
  show_states: true
  standard_format: true
  colorize_states: true
chart_type: line
series:
  - entity: sensor.average_electricity_price_today
    name: Prijs nu
    float_precision: 2
    type: column
    opacity: 1
    data_generator: |
      return entity.attributes.prices_today.map((record, index) => {
        return [new Date(record.time).getTime(), record.price];
      });
    color: '#4169E1'
    show:
      legend_value: false
      in_header: before_now
  - entity: sensor.average_electricity_price_today
    name: Zelfde tijd morgen
    float_precision: 2
    type: column
    opacity: 0.8
    color: '#00BFFF'
    data_generator: |
      return entity.attributes.prices_tomorrow.map((record, index) => {
        return [new Date(record.time).getTime()-86400000, record.price];
      });
    show:
      legend_value: false
      in_header: before_now
  - entity: sensor.injectie_average_electricity_price_today
    name: Injectie nu
    opacity: 1
    float_precision: 2
    type: column
    color: '#006400'
    data_generator: |
      return entity.attributes.prices_today.map((record, index) => {
        return [new Date(record.time).getTime(), record.price];
      });
    show:
      legend_value: false
      in_header: before_now
  - entity: sensor.injectie_average_electricity_price_today
    name: Injectie morgen
    float_precision: 2
    type: column
    color: '#9ACD32'
    opacity: 0.8
    data_generator: |
      return entity.attributes.prices_tomorrow.map((record, index) => {
        return [new Date(record.time).getTime()-86400000, record.price];
      });
    show:
      legend_value: false
      in_header: before_now
apex_config:
  datalabels:
    enabled: true
  yaxis:
    opposite: false
    reversed: false
    logarithmic: false
    decimalsInFloat: 3
    min: 0
    labels:
      show: true
    tooltip:
      enabled: true
    crosshairs:
      show: true
  xaxis:
    labels:
      show: true
      rotate: -45

4 Likes

I made it work for me in node-red, if anyones interested below is my flow for my dryer.

[{"id":"be5bf9ea2dbab7ee","type":"ha-entity","z":"69ddabec5f0865c0","name":"droger_prijs","server":"c358b7f9.9f5e18","version":2,"debugenabled":false,"outputs":1,"entityType":"sensor","config":[{"property":"name","value":"droger_prijs"},{"property":"device_class","value":""},{"property":"icon","value":""},{"property":"unit_of_measurement","value":"€"},{"property":"state_class","value":""},{"property":"last_reset","value":""}],"state":"price","stateType":"msg","attributes":[],"resend":true,"outputLocation":"payload","outputLocationType":"none","inputOverride":"allow","outputOnStateChange":false,"outputPayload":"","outputPayloadType":"str","x":1250,"y":220,"wires":[[]]},{"id":"899cfa6862a23187","type":"function","z":"69ddabec5f0865c0","name":"filter","func":"// Set the start and end times for the filter\nvar startTime = msg.starttime;\nvar endTime = msg.endtime;\n\n// Filter the array and return only the items that are between the start and end times\nmsg.payload = msg.data.attributes.prices.filter(function(object) {\n  return object.time >= startTime && object.time <= endTime;\n});\n\n// Return the filtered array\nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":830,"y":160,"wires":[["501a717164a4dcb2"]]},{"id":"e8178a02ee68eff3","type":"api-current-state","z":"69ddabec5f0865c0","name":"anwb prijzen","server":"c358b7f9.9f5e18","version":3,"outputs":1,"halt_if":"","halt_if_type":"str","halt_if_compare":"is","entity_id":"sensor.anwb_average_electricity_price_today","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":690,"y":160,"wires":[["899cfa6862a23187"]]},{"id":"5216460e14b19bfd","type":"function","z":"69ddabec5f0865c0","name":"add","func":"const offset = msg.offset;\nfunction addHours(numOfHours, date = new Date()) {\n  date.setTime(date.getTime() + numOfHours * 60 * 60 * 1000);\n\n  return date;\n}\n\n// payload = tijd+() \nmsg.payload = addHours(msg.offset);\nmsg.date=new Date ()\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":270,"y":160,"wires":[["356cd842a84cff14"]]},{"id":"356cd842a84cff14","type":"moment","z":"69ddabec5f0865c0","name":"endtime","topic":"","input":"payload","inputType":"msg","inTz":"ETC/UTC","adjAmount":"0","adjType":"hours","adjDir":"add","format":"YYYY-MM-DD HH:00:00+01:00","locale":"NL","output":"endtime","outputType":"msg","outTz":"Europe/Amsterdam","x":400,"y":160,"wires":[["defb11b4c94fc552"]]},{"id":"defb11b4c94fc552","type":"moment","z":"69ddabec5f0865c0","name":"starttime","topic":"","input":"date","inputType":"msg","inTz":"ETC/UTC","adjAmount":"1","adjType":"hours","adjDir":"add","format":"YYYY-MM-DD HH:00:00+01:00","locale":"NL","output":"starttime","outputType":"msg","outTz":"Europe/Amsterdam","x":540,"y":160,"wires":[["e8178a02ee68eff3"]]},{"id":"501a717164a4dcb2","type":"sort","z":"69ddabec5f0865c0","name":"","order":"ascending","as_num":true,"target":"payload","targetType":"msg","msgKey":"price","msgKeyType":"jsonata","seqKey":"payload","seqKeyType":"msg","x":950,"y":160,"wires":[["efc9c940ab56296e","72d9907fe8ed6346"]]},{"id":"efc9c940ab56296e","type":"function","z":"69ddabec5f0865c0","name":"Prijs","func":"msg.price = msg.payload[0].price.toFixed(2);\nreturn msg;\n\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1090,"y":200,"wires":[["be5bf9ea2dbab7ee"]]},{"id":"ac78c233b81c0884","type":"api-call-service","z":"69ddabec5f0865c0","name":"Set starttime Droger","server":"c358b7f9.9f5e18","version":5,"debugenabled":false,"domain":"input_datetime","service":"set_datetime","areaId":[],"deviceId":[],"entityId":["input_datetime.droger_timer"],"data":"{\"datetime\":\"{{payload}}\"}","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1260,"y":160,"wires":[[]]},{"id":"72d9907fe8ed6346","type":"function","z":"69ddabec5f0865c0","name":"Tijd","func":"msg.payload = msg.payload[0].time; \nreturn msg;\n\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1090,"y":160,"wires":[["ac78c233b81c0884","5f6f62984b4062a1"]]},{"id":"ca7af94dcf96ba9a","type":"server-state-changed","z":"69ddabec5f0865c0","name":"droger 3u","server":"c358b7f9.9f5e18","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"input_button.droger_3uur","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":true,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"offset","propertyType":"msg","value":"3.0","valueType":"str"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":120,"y":80,"wires":[["5216460e14b19bfd"]]},{"id":"bf6eed3ac571e0bb","type":"server-state-changed","z":"69ddabec5f0865c0","name":"droger 6u","server":"c358b7f9.9f5e18","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"input_button.droger_6uur","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":true,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"offset","propertyType":"msg","value":"6.0","valueType":"str"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":120,"y":120,"wires":[["5216460e14b19bfd"]]},{"id":"989f04a4bb36b66d","type":"server-state-changed","z":"69ddabec5f0865c0","name":"droger 12u","server":"c358b7f9.9f5e18","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"input_button.droger_12uur","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":true,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"offset","propertyType":"msg","value":"12.0","valueType":"str"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":120,"y":160,"wires":[["5216460e14b19bfd"]]},{"id":"200c028aa8282cdc","type":"server-state-changed","z":"69ddabec5f0865c0","name":"droger 24u","server":"c358b7f9.9f5e18","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"input_button.droger_24uur","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":true,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"offset","propertyType":"msg","value":"24.0","valueType":"str"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":120,"y":200,"wires":[["5216460e14b19bfd"]]},{"id":"577279dfe84ff6b4","type":"api-call-service","z":"69ddabec5f0865c0","name":"cancel starttime Droger","server":"c358b7f9.9f5e18","version":5,"debugenabled":false,"domain":"input_datetime","service":"set_datetime","areaId":[],"deviceId":[],"entityId":["input_datetime.droger_timer"],"data":"{\"datetime\":\"{{payload}}\"}","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1090,"y":280,"wires":[[]]},{"id":"2cab19150f9faee9","type":"server-state-changed","z":"69ddabec5f0865c0","name":"Cancel droger timer","server":"c358b7f9.9f5e18","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"input_button.droger_time_cancel","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":true,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"1111-11-11 11:11:11","valueType":"str"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":830,"y":220,"wires":[["577279dfe84ff6b4","f2f68b9df664ed0e"]]},{"id":"f2f68b9df664ed0e","type":"function","z":"69ddabec5f0865c0","name":"Prijs 0 (cancel)","func":"msg.price = 0;\nreturn msg;\n\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1060,"y":240,"wires":[["be5bf9ea2dbab7ee"]]},{"id":"5f6f62984b4062a1","type":"debug","z":"69ddabec5f0865c0","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1590,"y":120,"wires":[]},{"id":"c358b7f9.9f5e18","type":"server","name":"Home Assistant","version":1,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true}]

I really like your chart and I’m was looking to build something alike (but I think I’ll just stick with yours :-))
A question is where did you get the information (sensor) for the injection-rates?
I can’t seem to find it in entsoe?

Hi,

Gas prices could maybe found on the EnstoG platform : https://transparency.entsog.eu/

Fantastic addition to HA, keep up the good work!
A few questions:

  1. sensor.entso_e_energy_prices_average_electricity_price_today: will the numbers (state and attributes) include modifications based on the VAT tariff and template configured?
  2. When changing the config (like the template or VAT tariff), when can I see the updated numbers in for example the average price today? Or do I have to reload the plugin or disable/enable or restart HA?
  3. The average_price_today entity does not seem to change when I update VAT or the template. Even when removing, installing and configuring.
    Any ideas?

Had exactly the same… I removed the integration, added it back with a new name to force a renaming of the entities. That did the trick. Anything else continued to show the old tariffs.

By the way, anyone else noticed odd behaviour around midnight with the 0.3 version? Mix up of data?

Rolled back to 0.2 which solved the issue.

Ronald

Circling back to this topic, any chance you have deciphered the “PriceLevel” logic? I’m always getting “NORMAL” as result, even during the most expensive hours of the day :thinking: Is this due to the fact that Tibber compares prices from multiple days/longer period?

Related to this @JaccoR - “Current percentage of highest electricity price today” is this a calculation between current price vs highest price of the day or what is the outputted percentage? On GH it is more clearly stated as “Current Percentage Relative To Highest Electricity Price Of The Day”. I am wondering if this percentage could be actually used to discover the cheapest times of consumption? :thinking:

After all, we all do need hot water, heating etc on a daily basis, no matter if the price is relatively high for the longer period which Tibber seems to consider?

TLDR; I just want to beat the game and run electricity hungry devices when price is relatively cheap for current day and avoid the daily price peaks. Could be as simple as if current price < today’s average price = run hungry devices at full speed? :thinking:

Related to above, has anyone else had an issue with the daily average price today sensor updating to reflect next day prices when tomorrow prices are released?

For example today until 3pm Helsinki time, average price was around 14c/kwh. At 3pm average price for today sensor plummeted to 9c/kwh, which is the daily average for tomorrow.

Obviously this triggered Tibber price level to VERY_EXPENSIVE for the rest of the day which isn’t expected.

I did create simplified sensor based on the Tibber price level, which only checks if current market price is below or above daily average price, yet this sensor also got flipped to reflect tomorrow average price.

Am I missing something here or is there a bug in the today/tomorrow logic of the integration?

sensor.average_electricity_price_today does have two attributes, prices today and prices tomorrows - so data is there, but it seems the default state of the sensor.average_electricity_price_today seems to default into tomorrow prices? Or is it average for today and tomorrow?

Nevertheless, I think there’s a room for improvement there and something to look into.

1 Like

I didn’t get that far yet. So far I used Nordpool integration, but am migrating to entso atm.

What setting did you use in the Entso Integrtion? I am testing with “Sliding” right now.

Btw…is there any “4 Cheapest sequential Hours” Template I could “borrow” somewhere? For charging my PV battery I will go with non-sequential hours, but for my (not yet installed) heat pump I think 3-4 hours in a row is better.

There is one template above that shows 3 cheapest hours, it’s a binary sensor, basically saying true if cheapest 3 hours going or false if not.

I actually think I solved the average price calculation issue by changing the integration mode to rotating, it basically changes at midnight which is good enough for me. Sliding does make sense to some extent if today’s hourly prices are cheaper than tomorrow’s, but then again most of the devices I want to control needs to run daily anyways. So I’ll settle with day by day approach :sweat_smile:

If you want to combine best of both, it is actually pretty easy to create a custom template sensor that returns average price for today and tomorrow as separate sensors.

Here is a template I used:
{{ state_attr(“sensor.fortum_average_electricity_price_today”, “prices_today”) | map(attribute=‘price’) | average() }}

{{ state_attr(“sensor.fortum_average_electricity_price_today”, “prices_tomorrow”) | map(attribute=‘price’) | average() }}

1 Like

I have my first automation in test mode now, this is the one that charges my PV battery when energy is cheap.
This one is meant to choose individual hours, not consecutive hours.

I struggled a bit with the fact that it kept stopping and then not restarting, but I think I have it now.

It’s a time pattern trigger now that runs every 30 Minuted and checks two conditions…a binary sensor (electricity very cheap yes/no) and the SoC of my PV battery (I have a template that calculates “required charge” from solar forecast and battery capacity). If the current SoC is lower than the required charge and the price is “very cheap” it should start charging. I have another automation to make sure it stops.

Will test this and see how it goes. I might eventually move the time trigger from /30 to /1 (hour) but since my PV system sometimes fails on the first attempt I want it to try again.

I have similar setup, running time patter for every hour at first minute, this allows sensors to be updated etc before checking the sensor values. Basi now I have two automations one to adjust thermostats etc when electricity is very cheap, cheap or normal. And another one to check if price level is expensive, very expensive or extremely expensive.

It has been working fine.

What I will probably end up doing is creating automations for each scenario if I want more granular control.

Based on my experience, simple above/belov average price level isn’t a good choice as normal price can be higher than daily average. Then again, also Tibber price levels can be a bit too strict. Ie. 2c higher price can be considered very expensive, which may have been only 8-9c/kwh :man_shrugging: This happens quite often when daily average price is very low and night hours drives average down significantly.

Let’s see, not quite happy with the setup yet, but getting there.

1 Like

Perhaps try the ev charging integration? It’s meant for cars but it creates an on off sensor which you could use to trigger your battery. It has the option to define target soc and actual soc (by reference to a sensor) charging speed, Max tariff, min charge etc.

Use it for my ev. Works brilliantly. Effectively it calculates the required hours and pick the cheapest ones. You can also define a max amount you want to pay, for instance average price or average price * 0.8 - integration uses nordpool data.

https://github.com/jonasbkarlsson/ev_smart_charging

1 Like

Thanks. I already use that for my Tesla, works brilliantly. I had considered using it for the PV battery, but it would not work so well with the conditions I set I think.

But maybe I will try it if my own attempt fails