I have posted in WTH: WTH can’t we access all data from solar forecast as in energy dashboard. You can allways vote for it to be solved.
Mean while I have a workaround where you actually access the data from HA so there is no need for extra calls to the Forecast.Solar API. The data is only accessible through the web-socket API.
(I rushed this together this afternoon so I can’t guarantee it will work for everyone, and it most certainly contains some bugs.)
It requires Node Red and node-red-contrib-home-assistant-websocket
. Here is a flow you can import:
[{"id":"022a95a855f03d7e","type":"ha-api","z":"4d1a86a4c406178b","name":"","server":"993dd4ef.f733c8","version":1,"debugenabled":false,"protocol":"websocket","method":"get","path":"","data":"{\"type\":\"energy/solar_forecast\"}","dataType":"json","responseType":"json","outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"results"}],"x":350,"y":180,"wires":[["3855bddf.df6552"]]},{"id":"3855bddf.df6552","type":"function","z":"4d1a86a4c406178b","name":"Calculate","func":"// Fix values from HA forecast integration\nlet id = Object.keys(msg.payload)[0];\nconst whHours = msg.payload[id].wh_hours;\nvar newTable = {};\n\nObject.keys(whHours).forEach(key => {\n var t = new Date(key);\n if (whHours[key] == 0) {\n t.setMinutes(0,0,0);\n newTable[t.getTime()] = whHours[key];\n } else if (t.getMinutes() > 0) {\n t.setHours(t.getHours()+1,0,0,0);\n newTable[t.getTime()] = whHours[key];\n } else {\n newTable[t.getTime()] = whHours[key];\n }\n});\n\n// Time zone fixes\nlet firstKey = Object.keys(whHours)[0];\nconst tzOffset = firstKey.slice(-6);\nvar tzSeconds = parseInt(tzOffset.slice(1,3)*60*60 + tzOffset.slice(-2)*60)\nif (tzOffset.slice(0,1) === \"-\") {\n tzSeconds = -tzSeconds\n}\n\n// Create 48 hours array\nconst today = new Date();\nconst tomorrow = new Date();\ntomorrow.setDate(tomorrow.getDate() + 1);\n\nvar todayHours = [];\nvar tomorrowHours = [];\nfor (var i = 0; i < 24; i++) {\n today.setHours(i,0,0,0);\n todayHours.push(today.getTime());\n tomorrow.setHours(i,0,0,0);\n tomorrowHours.push(tomorrow.getTime());\n}\nallHours = todayHours.concat(tomorrowHours)\n\n// Create forecast table\nvar solarForecast = [];\nconst now = new Date(msg.timestamp + tzSeconds*1000);\nvar this_hour = null;\nvar next_hour = null;\n\nallHours.forEach(hour => {\n startDate = new Date(hour + tzSeconds*1000);\n stopDate = new Date(hour + tzSeconds*1000);\n stopDate.setHours(stopDate.getHours() + 1);\n if (msg.timestamp > hour) {\n this_hour = newTable[hour];\n next_hour = newTable[hour+3600000];\n }\n startStr = startDate.toISOString().slice(0,19) + tzOffset;\n stopStr = stopDate.toISOString().slice(0,19) + tzOffset;\n if (hour in newTable) {\n solarForecast.push({\n \"start\": startStr,\n \"end\": stopStr,\n \"value\": newTable[hour]\n });\n } else {\n solarForecast.push({\n \"start\": startStr,\n \"end\": stopStr,\n \"value\": 0\n });\n }\n});\n\nmsg.solar_forecast = solarForecast;\nmsg.this_hour = this_hour\nmsg.next_hour = next_hour\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":480,"y":180,"wires":[["6c9647caa4b673a9"]]},{"id":"6c9647caa4b673a9","type":"ha-entity","z":"4d1a86a4c406178b","name":"Solar PV Forecast","server":"993dd4ef.f733c8","version":2,"debugenabled":false,"outputs":1,"entityType":"sensor","config":[{"property":"name","value":"Solar PV Forecast"},{"property":"device_class","value":"energy"},{"property":"icon","value":"mdi:solar-power-variant"},{"property":"unit_of_measurement","value":"Wh"},{"property":"state_class","value":"measurement"},{"property":"last_reset","value":""}],"state":"this_hour","stateType":"msg","attributes":[{"property":"full_forecast","value":"solar_forecast","valueType":"msg"},{"property":"next_hour","value":"next_hour","valueType":"msg"}],"resend":true,"outputLocation":"payload","outputLocationType":"none","inputOverride":"allow","outputOnStateChange":false,"outputPayload":"","outputPayloadType":"str","x":650,"y":180,"wires":[[]]},{"id":"f3b80fbf412bc248","type":"server-state-changed","z":"4d1a86a4c406178b","name":"Forecast.Solar","server":"993dd4ef.f733c8","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"sensor.energy_next_hour","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":false,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"topic","propertyType":"msg","value":"forecast_uppdated","valueType":"str"},{"property":"timestamp","propertyType":"msg","value":"","valueType":"date"}],"x":180,"y":200,"wires":[["022a95a855f03d7e"]]},{"id":"9e794fb98f28d5e8","type":"inject","z":"4d1a86a4c406178b","name":"Every hour","props":[{"p":"topic","vt":"str"},{"p":"timestamp","v":"","vt":"date"}],"repeat":"3600","crontab":"","once":true,"onceDelay":0.1,"topic":"start","x":190,"y":160,"wires":[["022a95a855f03d7e"]]},{"id":"993dd4ef.f733c8","type":"server","name":"Home Assistant","version":4,"addon":false,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true,"heartbeat":false,"heartbeatInterval":30,"areaSelector":"friendlyName","deviceSelector":"friendlyName","entitySelector":"friendlyName","statusSeparator":"at: ","statusYear":"hidden","statusMonth":"short","statusDay":"numeric","statusHourCycle":"h23","statusTimeFormat":"h:m"}]
It will create a new sensor named sensor.solar_pv_forecast
. The state of the sensor is this hours predicted production (the same as Estimated Energy Production - This Hour (in kWh) with the Forecast.Solar integration). But it also has an attribute named full_forecast
which contains a list of all hours prediction for this day and the next day. So in total 48 hours prediction or forecast.
Here is an exampel how to visualize it useing a apexcharts-card:
type: custom:apexcharts-card
header:
show: false
show_states: true
title: Solar forecast
now:
show: true
label: Now
graph_span: 2d
span:
start: day
series:
- entity: sensor.solar_pv_forecast
data_generator: |
return entity.attributes.full_forecast.map((entry) => {
return [new Date(entry.start), entry.value];
});
offset: '-30min'
name: Forecast
type: column
color: darkgreen
opacity: 0.4
stroke_width: 1
show:
in_header: false
legend_value: false