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!