Using Home Assistant to calculate my energy bill using data from my Solar Inverter

Yeah, it’s more of an environmental, political and future proofing motivation rather than a financial one.

After I first got the solar diverter, I sat down and did the sums at the time and only then did I realise that my solar export tariff was 9.5cents per kW at the time and my controlled load was actually cheaper at 9.1088 cents per Kw, making it actually cheaper to use controlled load instead of the diverter! This initially made me feel really silly but I know the solar export tariff is always trending down so eventually it will make better financial sense. As of October 2021, the tariff dropped 20% down to 7.6c per kWh which is what prompted me to change power companies.

The environmental reasoning is that it’s a good thing that i’m not using coal produced power in the middle of the night to heat my hot water.
Political reason is that the power company keeps devaluing the power I export so why should I just let them have all of it for cheap when I can make better use of it myself instead?
Eventually the export tariff will be 0 in a few years and who’s to say they will stop at 0?
They might even keep on going and charge you to export your power for ‘stressing out the grid’ as they are already starting claim. That would substantially raise the value of higher solar self consumption.
Regardless of what happens in the future, I will still have this device which will be helping to maximise my solar self consumption and slightly reduce my reliance on coal.

Another possible way to classify what a solar diverter does is that in a roundabout way my hot water bottle can now be classified as a solar battery. I mean it is storing solar energy for later use much like a lithium battery. The only difference between a hot water bottle and a lithium battery is the type of energy being stored and the way that energy is used. Ive just turned my hot water bottle into a budget Tesla powerwall battery in a roundabout way :slight_smile:. A Telsa powerwall holds 14kWh, my hot water system will take about 12 kWh to ‘recharge’. Not that much different. Hot water Energy is still energy.
All of these low production day issues we have discussed would also apply to a Tesla power wall as well, it just may not directly impact hot water depending on what you choose to do with the power wall energy. Can you imagine paying $14,000 for a Tesla power wall just to use most of its 14kWh of energy to heat your hot water in the middle of the night just so you can achieve grid independence? Now that definitely wouldn’t make financial sense compared to the solar diverter. I’ve paid for something that is 15 times cheaper or only 6% of the total cost of a Tesla power wall which sort of does the same thing (if used mainly for hot water) and stores roughly a similar amount of energy. Maybe this is how I can rationalise the $900 purchase to myself.

Another potential way to soak up excess solar energy and make a better return than the solar export tariff is to setup some crypto miners to switch on only when there is enough excess solar generated energy to accomodate them. Only drawback is that they are very loud so you would need a dry, ventilated area where the screaming noise of the fans wont bother anyone in the day. Thats sort of what I might do next.

1 Like

Totally agree with everything you say. It would still be a nice feeling to be using the generated power. Interesting idea about the mining!

1 Like

Ah yes, I forgot to mention, both diverters have a ‘boost’ switch/button for emergencies to instantly pull power from the grid to reheat the water immediately. I wouldn’t dare pull the full 10 kWh within the peak tariff. 10 kWh * $0.322 = $3.22 but maybe I’m just cheap :slight_smile:

If only, like I’ve said, we could have HA control over these devices. Then we could do things like look at the solar forecast for tomorrow and give the heater a boost overnight, etc.

What is this solar forecast you are talking about?

I just signed up with solcast.com.au
Allows 10 API pull requests a day for free.
See if I can work this into automation somehow.

Time for me to give something back! See https://community.home-assistant.io/t/rest-api-command-platform-fails-on-post-to-external-url-solcast/. It’s a long thread, but comes to some very good conclusions. Here’s what I ended up with:

In my rest.yaml config file:

###########################
# Get Solcast Forecast Data
###########################

- resource: !secret solcast_forecast_resource
  #resource: https://api.solcast.com.au/rooftop_sites/RESOURCE_ID/forecasts?format=json&api_key=API_KEY&hours=72
  scan_interval: '00:30:00' # RATE LIMIT!
  sensor:
  - name: "Solcast Forecast Data"
    force_update: true
    value_template: "{{ value_json.forecasts[0].pv_estimate|round(2) }}"
    unit_of_measurement: "kW"
    device_class: power
    json_attributes:
    - forecasts
  - name: "Solcast Forecast 10"
    force_update: true
    value_template: "{{ value_json.forecasts[0].pv_estimate10|round(2) }}"
  - name: "Solcast Forecast 90"
    force_update: true
    value_template: "{{ value_json.forecasts[0].pv_estimate90|round(2) }}"

In my templates.yaml config file:

sensor:
    ##################
    # Solcast sensors
    ##################
  - name: "Solcast Forecast Energy Today"
    unique_id: solcast_forecast_energy_today
    unit_of_measurement: "kWh"
    device_class: energy
    state: >
      {% set ns = namespace (fc_today = 0) %}
      {% for forecast in state_attr('sensor.solcast_forecast_data', 'forecasts')|default('variable is not defined') %}
        {% set daydiff = as_local(strptime(forecast.period_end, '%Y-%m-%dT%H:%M:%S.%f0Z').replace(tzinfo=utcnow().tzinfo)).date() - as_local(utcnow()).date() %} 
        {% if daydiff.days == 0 %}
          {% set ns.fc_today = ns.fc_today + (forecast.pv_estimate/2)|float %}
        {%- endif %}
      {%- endfor %}
      {{ ns.fc_today|round(2) }}
  - name: "Solcast Forecast Energy Tomorrow"
    unique_id: solcast_forecast_energy_tomorrow
    unit_of_measurement: "kWh"
    device_class: energy
    state: >
      {% set ns = namespace (fc_tommorrow = 0) %}
      {% for forecast in state_attr('sensor.solcast_forecast_data', 'forecasts')|default('variable is not defined') %}
        {% set daydiff = as_local(strptime(forecast.period_end, '%Y-%m-%dT%H:%M:%S.%f0Z').replace(tzinfo=utcnow().tzinfo)).date() - as_local(utcnow()).date() %} 
        {% if daydiff.days == 1 %}
          {% set ns.fc_tommorrow = ns.fc_tommorrow + (forecast.pv_estimate/2)|float %}
        {%- endif %}
      {%- endfor %}
      {{ ns.fc_tommorrow|round(2) }}
  - name: "Solcast Forecast Peak Power Today"
    unique_id: solcast_forecast_peak_power_today
    unit_of_measurement: "kW"
    device_class: power
    state_class: measurement
    state: >
      {% set ns = namespace (fc_today_max = 0) %}
      {% for forecast in state_attr('sensor.solcast_forecast_data', 'forecasts')|default('variable is not defined') %}
        {% set daydiff = as_local(strptime(forecast.period_end, '%Y-%m-%dT%H:%M:%S.%f0Z').replace(tzinfo=utcnow().tzinfo)).date() - as_local(utcnow()).date() %} 
        {% if daydiff.days == 0 %}
          {% if ns.fc_today_max < forecast.pv_estimate|float %}
            {% set ns.fc_today_max = forecast.pv_estimate|float %}
          {%- endif %}
        {%- endif %}
      {%- endfor %}
      {{ ns.fc_today_max|round(2) }}
  - name: "Solcast Forecast Peak Power Tomorrow"
    unique_id: solcast_forecast_peak_power_tomorrow
    unit_of_measurement: "kW"
    device_class: power
    state_class: measurement
    state: >
      {% set ns = namespace (fc_tomorrow_max = 0) %}
      {% for forecast in state_attr('sensor.solcast_forecast_data', 'forecasts')|default('variable is not defined') %}
        {% set daydiff = as_local(strptime(forecast.period_end, '%Y-%m-%dT%H:%M:%S.%f0Z').replace(tzinfo=utcnow().tzinfo)).date() - as_local(utcnow()).date() %} 
        {% if daydiff.days == 1 %}
          {% if ns.fc_tomorrow_max < forecast.pv_estimate|float %}
            {% set ns.fc_tomorrow_max = forecast.pv_estimate|float %}
          {%- endif %}
        {%- endif %}
      {%- endfor %}
      {{ ns.fc_tomorrow_max|round(2) }}
  - name: "Solcast Forecast Peak Time Today"
    unique_id: solcast_forecast_peak_time_today
    state: >
      {% set ns = namespace (fc_today_max = 0, fc_today_max_time = 0) %}
      {% for forecast in state_attr('sensor.solcast_forecast_data', 'forecasts')|default('variable is not defined') %}
        {% set daydiff = as_local(strptime(forecast.period_end, '%Y-%m-%dT%H:%M:%S.%f0Z').replace(tzinfo=utcnow().tzinfo)).date() - as_local(utcnow()).date() %} 
        {% if daydiff.days == 0 %}
          {% if ns.fc_today_max < forecast.pv_estimate|float %}
            {% set ns.fc_today_max = forecast.pv_estimate|float %}
            {% set ns.fc_today_max_time = as_local(strptime(forecast.period_end, '%Y-%m-%dT%H:%M:%S.%f0Z').replace(tzinfo=utcnow().tzinfo)).time() %}
          {%- endif %}
        {%- endif %}
      {%- endfor %}
      {{ ns.fc_today_max_time }}
  - name: "Solcast Forecast Peak Time Tomorrow"
    unique_id: solcast_forecast_peak_time_tomorrow
    state: >
      {% set ns = namespace (fc_tomorrow_max = 0, fc_tomorrow_max_time = 0) %}
      {% for forecast in state_attr('sensor.solcast_forecast_data', 'forecasts')|default('variable is not defined') %}
        {% set daydiff = as_local(strptime(forecast.period_end, '%Y-%m-%dT%H:%M:%S.%f0Z').replace(tzinfo=utcnow().tzinfo)).date() - as_local(utcnow()).date() %} 
        {% if daydiff.days == 1 %}
          {% if ns.fc_tomorrow_max < forecast.pv_estimate|float %}
            {% set ns.fc_tomorrow_max = forecast.pv_estimate|float %}
            {% set ns.fc_tomorrow_max_time = as_local(strptime(forecast.period_end, '%Y-%m-%dT%H:%M:%S.%f0Z').replace(tzinfo=utcnow().tzinfo)).time() %}
          {%- endif %}
        {%- endif %}
      {%- endfor %}
      {{ ns.fc_tomorrow_max_time }}
  - name: "Solcast Forecast Power Next Hour"
    unique_id: solcast_forecast_power_next_hour
    state: >
      {{ ((state_attr('sensor.solcast_forecast_data', 'forecasts')[0].pv_estimate|default('variable is not defined') + state_attr('sensor.solcast_forecast_data', 'forecasts')[1].pv_estimate|default('variable is not defined'))/2)|round(2) }}
    unit_of_measurement: 'kW'
    device_class: power
  - name: "Solcast Forecast Power Next 12 Hours"
    unique_id: solcast_forecast_power_next_12_hours
    state: >
      {% set ns = namespace (fc_next12 = 0) %}
      {% for i in range(0, 23) %}
        {% set ns.fc_next12 = ns.fc_next12 + states.sensor.solcast_forecast_data.attributes['forecasts'][i]['pv_estimate']|float %}
      {%- endfor %}
      {{ ns.fc_next12|round(2) }}
    unit_of_measurement: 'kW'
    device_class: power
  - name: "Solcast Forecast Power Next 24 Hours"
    unique_id: solcast_forecast_power_next_24_hours
    state: >
      {% set ns = namespace (fc_next24 = 0) %}
      {% for i in range(0, 47) %}
        {% set ns.fc_next24 = ns.fc_next24 + states.sensor.solcast_forecast_data.attributes['forecasts'][i]['pv_estimate']|float %}
      {%- endfor %}
      {{ ns.fc_next24|round(2) }}
    unit_of_measurement: 'kW'
    device_class: power
  - name: "Solcast Forecast Energy Current Hour"
    unique_id: solcast_forecast_energy_current_hour
    state: >
      {% set ns = namespace (fc_energy_current = 0) %}
      {% for i in range(0, 1) %}
        {% set ns.fc_energy_current = ns.fc_energy_current + (states.sensor.solcast_forecast_data.attributes['forecasts'][i]['pv_estimate']/2)|float %}
      {%- endfor %}
      {{ ns.fc_energy_current|round(2) }}
    unit_of_measurement: 'kWh'
    device_class: energy
  - name: "Solcast Forecast Energy Next Hour"
    unique_id: solcast_forecast_energy_next_hour
    state: >
      {% set ns = namespace (fc_energy_next = 0) %}
      {% for i in range(2, 3) %}
        {% set ns.fc_energy_next = ns.fc_energy_next + (states.sensor.solcast_forecast_data.attributes['forecasts'][i]['pv_estimate']/2)|float %}
      {%- endfor %}
      {{ ns.fc_energy_next|round(2) }}
    unit_of_measurement: 'kWh'
    device_class: energy

Then an apex-charts-card:

graph_span: 48h
span:
  start: day
  offset: '-0h'
header:
  show: true
  title: Solar Production vs. forecast
  show_states: true
now:
  show: true
  label: now
apex_config:
  legend:
    show: false
series:
  - entity: sensor.envoy_current_power_production
    stroke_width: 2
    show:
      extremas: false
    color: '#32CD32'
    name: Actual
    unit: W
    fill_raw: last
    extend_to_end: false
    group_by:
      func: avg
      duration: 30min
  - entity: sensor.solcast_forecast_data
    stroke_width: 2
    show:
      extremas: false
    color: '#3498DB'
    transform: return x * 1000;
    name: Forecast
    unit: W
    fill_raw: last
    extend_to_end: false
  - entity: sensor.solcast_forecast_10
    stroke_width: 2
    show:
      extremas: false
      in_header: false
    color: '#797D7F'
    transform: return x * 1000;
    name: Forecast 10
    unit: W
    fill_raw: last
    extend_to_end: false
    opacity: 0.4
  - entity: sensor.solcast_forecast_90
    stroke_width: 2
    show:
      extremas: false
      in_header: false
    color: '#797D7F'
    transform: return x * 1000;
    name: Forecast 90
    unit: W
    fill_raw: last
    extend_to_end: false
    opacity: 0.4
  - entity: sensor.solcast_forecast_data
    stroke_width: 2
    show:
      extremas: false
      in_header: false
    color: '#E74C3C'
    type: line
    extend_to_end: false
    unit: W
    data_generator: |
      return entity.attributes.forecasts.map((entry) => {
         return [new Date(entry.period_end), entry.pv_estimate*1000];
       });
  - entity: sensor.solcast_forecast_data
    stroke_width: 2
    show:
      extremas: false
      in_header: false
    color: '#797D7F'
    type: line
    extend_to_end: false
    unit: W
    opacity: 0.4
    data_generator: |
      return entity.attributes.forecasts.map((entry) => {
         return [new Date(entry.period_end), entry.pv_estimate10*1000];
       });
  - entity: sensor.solcast_forecast_data
    stroke_width: 2
    show:
      extremas: false
      in_header: false
    color: '#797D7F'
    type: line
    extend_to_end: false
    unit: W
    opacity: 0.4
    data_generator: |
      return entity.attributes.forecasts.map((entry) => {
         return [new Date(entry.period_end), entry.pv_estimate90*1000];
       });

sensor.solcast_forecast_energy_tomorrow always gives you tomorrow’s forecast.

This is not my own work, just an amalgamation of that of the people over on that thread. But it seems to work beautifully. Hope it saves you some time, after all the time you’ve saved me!

2 Likes

Cool stuff. Thanks for that

I added this as a custom repo

And now my forecast works

2 Likes

Ok, so this, this I can work into automations if I feel compelled to do.

Very cool. Thanks.

This is much better than trying to automate a pool pump based on ‘sunny’ or other various descriptions.

Ok, you’ve completely lost me! There’s an integration that does all this, rather than adding all that code above, gleaned from the other thread? And is that dotted line the forecast line? How does that make it into the HA Energy component? I couldn’t find the integration using HACS or normal HA integrations. How is it added?

Yes, I started by looking at the various weather descriptions, but they’re very coarse.

Apparently so.

The included instructions are terrible.
Here is how I did it.
Not sure if you have the HACS add on yet.
I already do, so this is how once you have it.

Open up HACS

Tap on integrations

Tap the 3 dots and then custom repositories

Paste this into the repo field and press add https://github.com/oziee/ha-solcast-solar

Then go to configure / integrations / add new and search for solcast and paste in the api key and the site id

Then in configure /energy you can add forecast

1 Like

I guess the integration must mimic how the official forecast.solar integration (which doesn’t support Australia) works, because my energy configuration looks like this:

There seems to be an open issue with too many API calls. Has it been running well for you so far? The one thing that’s missing is the 10/90% probability stats by the looks of it, but that’s no big deal.

I don’t understand why the thread I mentioned, which is very current, doesn’t mention this integration!

Would you mind posting the yaml for your dashboard? Always keen to learn how people put these things together…

It updates once an hour and takes a break from 7pm to 5am. I just checked my solcast dashboard.

API Usage

Daily Usage Limit
50 requests

Daily Usage Consumed
26 requests

The consumed usage does not reset until midnight UTC

It’s currently 8pm utc, I think it will make it another 4 hours before counter resets.

The description of the add on says

Modified from the great works of

  • dannerph/homeassistant-solcast
  • cjtapper/solcast-py
  • home-assistant-libs/forecast_solar

Appears to combine elements form those 3 GitHub repos

Ok, great. I’ll give it a go. It will be nice to have the forecast integrated with the Energy component. Thanks again.

I just did the sums.

There are 5 sensors and I am assuming each sensor consumes 1 api call.
(7pm) 19 - 5 (5am) = 14 active hours.
14 * 5 calls per hour = 70 requests per day.
if I change the slider to 2 hours per request, then it halves to 7 requests per day * 5 sensors = 35 requests per day.
Given the free accounts are limited to 50 per day, I am going with 2 hours on the slider.

Screen Shot 2021-10-15 at 8.30.52 am

Not anymore. All my graphs are now in UTC time instead of +11hours

I don’t think so. If you look at the REST call in the stuff I posted above, I think one api call gets all the data in one go. The other thread goes into a lot of detail on the data formats. If you look at Issues on the repo, there is more discussion on the frequency too.

Hmmm - which graphs exactly?

appears to be a bug in home assistant.
The graphs show fine in iOS app.
while when accessing using web browser, the graphs are in UTC.

What graphs? all graphs. even the history tab is all in utc via web browser.

Same graph when viewed on my mobile via the iOS app which formats perfectly.

Every time you post something, I see something interesting! What do you have on your 1H, 12H, 24H tabs?

The same graphs with the same elements, just with a different history length (hours to show) for better clarity.