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

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.

Always learning…

1 Like

I’ve been playing with the solcast integration. In the Energy graphs you included above, you showed one which was presumably for tomorrow, with a nice sloping graph of the forecast. Any idea how this is produced from the integration’s sensors, which don’t seem to provide enough data to do that?

Pretty much have to learn python and pick apart the repo.

Either that or ask oziee (Greg) · GitHub directly :slight_smile:

Yeh. Must be feeding the data directly to the Energy platform, and not creating sensors for it all (which is what the other method does). Oh well, moot point until the integration is behaving a little better. Hope it gets there.

Hey @Sddawson,

I had a rainly/low solar production day and thought I would show you what happened.

This is a typical sunny day where the hot water is done heating up by 11am.

This is a rainy day where I managed to still generate 12.6 kWh and every bit of it went to the hot water heater all day. I went out and checked the temp sensor late in the afternoon and the water was at 70 degrees celcius.