Access data from Forecast.Solar

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

3 Likes