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