Skip to content

Instantly share code, notes, and snippets.

@dirkjanfaber
Last active April 18, 2024 08:03
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dirkjanfaber/d83d3224f241ec4abf6f9f119bbee9cc to your computer and use it in GitHub Desktop.
Save dirkjanfaber/d83d3224f241ec4abf6f9f119bbee9cc to your computer and use it in GitHub Desktop.
Solar yield forecasting using forecast.solar

This subflow uses the http request node to fetch solar forecasts for geographical positions, using the API from https://forecast.solar/. Please check their website and consider getting a paid account.

Solar forecast flow

Do note that, on a free account, you are limited in the number of requests to do. Also note that the data only gets updated once every 15 minutes, so there is no reason to query more often. There is rate limiting built in the subflow not to perform requests more than once every 15 minutes.

Configuration

Solar forecast properties

It uses the parameters as described on: http://doc.forecast.solar/doku.php

  • :apikey - personal API key for registered users
  • :lat - latitude of location, -90 (south) … 90 (north); Internal precission is 0.0001 or abt. 10 m
  • :lon - longitude of location, -180 (west) … 180 (east); Internal precission is 0.0001 or abt. 10 m
  • :dec - plane declination, 0 (horizontal) … 90 (vertical); Internal precission is integer
  • :az - plane azimuth, -180 … 180 (-180 = north, -90 = east, 0 = south, 90 = west, 180 = north); Internal precission is integer
  • :kwp - installed modules power in kilo watt peak (kWp)

You can choose between 3 different type of requests. Note that only estimate is available on the free plan.

In case of estimates, one of the following options can be selected:

  • watts - Watts (power) average for the period
  • watt_hours_period - Watt hours (energy) for the period
  • watt_hours - Watt hours (energy) summarized over the day
  • watt_hours_day - Watt hours (energy) summarized for each day

For the graph output there are some extra settings available:

  • Output in kWh - when checked output can be set to kWh instead of Wh
  • Show todays forecast - whether or not to include todays forecast
  • Days to forecast - the number of days to forecast (excluding today). Note that you can not get more days forecasted than your API key allows.
  • Widen graph - widen the graph to only show non-zero values
  • Show day instead of date - Show the day instead of the date in the series

The optional horizon field can be filled out in case an object blocks your solar panels from the sun. See the description here on what numbers to fill out. Leave it empty if you have no objects blocking your panels.

Input

It triggers when injecting a message into the node.

Output

There are two outputs. The first output is an object with the result and a status message stored into the msg.payload.

Most important is the msg.payload.result, which contains the estimated production of the panels. E.g.:

payload: object
  result: object
    2022-11-28: 23
    2022-11-29: 35

The msg.payload.message gives information on how successful the query was, the exitcode of the query and the status of the rate limit (how many queries you have left).

The second output can be directly linked to a line or a bar chart, quickly giving a once-glance overview for the predicted forecast.

Forecast chart

Status

Initially the status of the note will be a blue dot, showing "Unknown limit", as it is unaware of the set ratelmits. After the first request, the returned ratelimit will be put in the text in the form of remaining/limit. If more than half the limit is remaining, the dot will be green. If less then half the limit is remaining, the dot will be yellow. If no limit is left, the dot will turn red. Please keep in mind that the ratelimit will be reset after one hour, so you can send a new request after that hour.

Example: stacked chart

If you have solar panels facing different directions, it might be helpful to have them both together in the same chart.

Example: stacked chart flow

The following (part of) the flow does exactly that. Import this and link it to the second output of the solar forecast node (as in the screenshot):

[{"id":"043b44c03a398e65","type":"ui_chart","z":"d378cf57268ffe49","name":"","group":"c616f24820f867fe","order":9,"width":"24","height":"8","label":"Combined","chartType":"bar","legend":"true","xformat":"HH:mm:ss","interpolate":"linear","nodata":"","dot":false,"ymin":"","ymax":"","removeOlder":1,"removeOlderPoints":"","removeOlderUnit":"3600","cutout":0,"useOneColor":true,"useUTC":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"outputs":1,"useDifferentColor":false,"className":"","x":950,"y":660,"wires":[[]]},{"id":"8609aef758bafece","type":"change","z":"d378cf57268ffe49","name":"","rules":[{"t":"set","p":"solar.east","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":490,"y":640,"wires":[["1df1050aaf92a8e6"]]},{"id":"fa2b24315e845352","type":"change","z":"d378cf57268ffe49","name":"","rules":[{"t":"set","p":"solar.west","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":490,"y":680,"wires":[["1df1050aaf92a8e6"]]},{"id":"1df1050aaf92a8e6","type":"function","z":"d378cf57268ffe49","name":"Combine multiple inputs","func":"let solar = flow.get('solar') || []\nif (solar.length === 0) {\n  return;\n}\n\nlet series = []\nlet data = []\nlet labels = []\n\nfor (const key in solar) {\n    for (const s in solar[key][0].series) {\n        series.push(solar[key][0].series[s] + ' ' + key)\n    }\n    for (const d in solar[key][0].data) {\n        data.push(solar[key][0].data[d])\n    }\n    // data.push(solar[key][0].data[0])\n    labels = solar[key][0].labels\n\n}\n\nmsg.payload = [{\n    series,\n    data,\n    labels\n    }]\n\nmsg.ui_control = {\n     options: {\n        scales: {\n            xAxes: [{\n                stacked: true\n            }],\n            yAxes: [{\n                stacked: true\n            }]\n        }\n    }\n}\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":730,"y":660,"wires":[["043b44c03a398e65"]]},{"id":"c616f24820f867fe","type":"ui_group","name":"Visualisations","tab":"dbb3a86540d4aef4","order":5,"disp":true,"width":"24","collapse":false,"className":""},{"id":"dbb3a86540d4aef4","type":"ui_tab","name":"Solar predictions","icon":"dashboard","disabled":false,"hidden":false}]

Note that this does take the legend label from the name in the change nodes. Adjust the naming there if you want to use different names.

The reason that above is only part of the flow is to prevent you importing the solar forecast subflow twice.

Example: stacked chart

[
{
"id": "93f485839dfa27a8",
"type": "subflow",
"name": "Solar forecast",
"info": "This subflow uses the http request node to fetch solar forecasts for geographical positions, using the API from https://forecast.solar/. Please check their website and consider getting a paid account.\n\nDo note that, on a free account, you are limited in the number of requests to do. Also note that the data only gets updated once every 15 minutes, so there is no reason to query more often. There is rate limiting built in the subflow not to perform requests more than once every 15 minutes.\n\n# Configuration\n\nIt uses the parameters as described on: http://doc.forecast.solar/doku.php\n\n - `:apikey` - personal API key for registered users\n - `:lat` - latitude of location, -90 (south) … 90 (north); Internal precission is 0.0001 or abt. 10 m\n - `:lon` - longitude of location, -180 (west) … 180 (east); Internal precission is 0.0001 or abt. 10 m\n - `:dec` - plane declination, 0 (horizontal) … 90 (vertical); Internal precission is integer\n - `:az` - plane azimuth, -180 … 180 (-180 = north, -90 = east, 0 = south, 90 = west, 180 = north); Internal precission is integer\n - `:kwp` - installed modules power in kilo watt peak (kWp)\n\nYou can choose between 3 different type of requests. Note that only `estimate` is available on the free plan.\n\n- `estimate` - this is the forecasted estimate that your panels should produce (given the right parameter settings)\n- `history` - historycal data\n- `clear sky` - estimate given if there would be a clear sky tomorrow\n\nIn case of estimates, one of the following options can be selected:\n- `watts` - Watts (power) average for the period\n- `watthours/period` - Watt hours (energy) for the period\n- `watthours` - Watt hours (energy) summarized over the day\n- `watthours/day` - Watt hours (energy) summarized for each day\n\nFor the graph output there are some extra settings available:\n\n- _Output in kWh_ - when checked output can be set to kWh instead of Wh\n- _Show todays forecast_ - whether or not to include todays forecast\n- _Days to forecast_ - the number of days to forecast (excluding today). Note that you can not get more days forecasted than your API key allows.\n- _Widen graph_ - widen the graph to only show non-zero values\n- _Show day instead of date_ - Show the day instead of the date in the series and labels\n\nThe optional _horizon_ field can be filled out in case an object blocks\nyour solar panels from the sun. See the description [here](https://doc.forecast.solar/horizon)\non what numbers to fill out.\nLeave it empty if you have no objects blocking your panels.\n\n# Input \n\nThe input is for triggering the solar forecast request. \nIt triggers when injecting a message into the node.\n\n# Output\n\nThere are two outputs. The first output is an object with the result and a status message stored into the `msg.payload`.\n\nMost important is the `msg.payload.result`, which contains the estimated production of the panels. E.g.:\n\n```\npayload: object\n result: object\n 2022-11-28: 23\n 2022-11-29: 35\n```\n\nThe `msg.payload.message` gives information on how successful the query was, the exitcode of the query and the status of the rate limit (how many queries you have left).\n\nThe **second** output can be directly linked to a line or a bar chart, quickly giving a once-glance overview for the predicted forecast.\n\n# Status\n\nInitially the status of the note will be a blue dot, showing \"_Unknown limit_\", as it is unaware of the set ratelmits. After the first request, the returned ratelimit will be put in the text in the form of `remaining/limit`. If more than half the limit is remaining, the dot will be green. If less then half the limit is remaining, the dot will be yellow. If no limit is left, the dot will turn red.\nPlease keep in mind that the ratelimit will be reset after one hour, so you can send a new request after that hour.\n\nIf something is wrong in the API request, the dot will turn red\nand the message will contain the msg.payload with the error. This\nhappens typically when the API is temporally down for maintenance.",
"category": "",
"in": [
{
"x": 240,
"y": 100,
"wires": [
{
"id": "c8dc6aa14b9f3e92"
}
]
}
],
"out": [
{
"x": 760,
"y": 260,
"wires": [
{
"id": "2f42837904c91d73",
"port": 0
},
{
"id": "fcc8d69a3ab88e6d",
"port": 0
}
]
},
{
"x": 770,
"y": 340,
"wires": [
{
"id": "5b0a430fb61e70e7",
"port": 0
}
]
}
],
"env": [
{
"name": "latitude",
"type": "num",
"value": "51.3",
"ui": {
"icon": "font-awesome/fa-location-arrow",
"label": {
"en-US": "Latitude"
},
"type": "input",
"opts": {
"types": [
"num"
]
}
}
},
{
"name": "longitude",
"type": "num",
"value": "5.6",
"ui": {
"icon": "font-awesome/fa-location-arrow",
"label": {
"en-US": "Longitude"
},
"type": "input",
"opts": {
"types": [
"num"
]
}
}
},
{
"name": "declination",
"type": "num",
"value": "37",
"ui": {
"icon": "font-awesome/fa-chevron-up",
"label": {
"en-US": "Declination"
},
"type": "input",
"opts": {
"types": [
"num"
]
}
}
},
{
"name": "azimuth",
"type": "num",
"value": "0",
"ui": {
"icon": "font-awesome/fa-compass",
"label": {
"en-US": "Azimuth"
},
"type": "spinner",
"opts": {
"min": -180,
"max": 180
}
}
},
{
"name": "modules power",
"type": "num",
"value": "1",
"ui": {
"icon": "font-awesome/fa-power-off",
"label": {
"en-US": "Modules power (kWp)"
},
"type": "input",
"opts": {
"types": [
"num"
]
}
}
},
{
"name": "apikey",
"type": "cred",
"ui": {
"icon": "font-awesome/fa-key",
"label": {
"en-US": "API key"
},
"type": "input",
"opts": {
"types": [
"cred"
]
}
}
},
{
"name": "type",
"type": "str",
"value": "estimate",
"ui": {
"label": {
"en-US": "Type"
},
"type": "select",
"opts": {
"opts": [
{
"l": {
"en-US": "Estimate"
},
"v": "estimate"
},
{
"l": {
"en-US": "History"
},
"v": "history"
},
{
"l": {
"en-US": "Clear sky"
},
"v": "clearsky"
}
]
}
}
},
{
"name": "watt",
"type": "str",
"value": "watts",
"ui": {
"icon": "font-awesome/fa-question-circle-o",
"label": {
"en-US": "Watt"
},
"type": "select",
"opts": {
"opts": [
{
"l": {
"en-US": "Watts (power) average for the period"
},
"v": "watts"
},
{
"l": {
"en-US": "Watt hours (energy) for the period"
},
"v": "watthours/period"
},
{
"l": {
"en-US": "Watt hours (energy) summarized over the day"
},
"v": "watthours"
},
{
"l": {
"en-US": "Watt hours (energy) summarized for each day"
},
"v": "watthours/day"
}
]
}
}
},
{
"name": "kwhoutput",
"type": "bool",
"value": "false",
"ui": {
"label": {
"en-US": "Output in kWh (in the graph)"
},
"type": "checkbox"
}
},
{
"name": "showtoday",
"type": "bool",
"value": "true",
"ui": {
"label": {
"en-US": "Show todays forecast"
},
"type": "checkbox"
}
},
{
"name": "daystoforecast",
"type": "str",
"value": "-1",
"ui": {
"label": {
"en-US": "Days to forecast"
},
"type": "select",
"opts": {
"opts": [
{
"l": {
"en-US": "Max"
},
"v": "-1"
},
{
"l": {
"en-US": "0"
},
"v": "0"
},
{
"l": {
"en-US": "1"
},
"v": "1"
},
{
"l": {
"en-US": "2"
},
"v": "2"
},
{
"l": {
"en-US": "3"
},
"v": "3"
},
{
"l": {
"en-US": "4"
},
"v": "4"
},
{
"l": {
"en-US": "5"
},
"v": "5"
},
{
"l": {
"en-US": "6"
},
"v": "6"
}
]
}
}
},
{
"name": "widengraph",
"type": "bool",
"value": "true",
"ui": {
"label": {
"en-US": "Widen graph"
},
"type": "checkbox"
}
},
{
"name": "showday",
"type": "bool",
"value": "false",
"ui": {
"label": {
"en-US": "Show day instead of date"
},
"type": "checkbox"
}
},
{
"name": "horizon",
"type": "str",
"value": "",
"ui": {
"icon": "font-awesome/fa-tree",
"label": {
"en-US": "(optional) horizon"
},
"type": "input",
"opts": {
"types": [
"str"
]
}
}
}
],
"meta": {
"module": "Solar Forecast",
"version": "0.0.14",
"author": "dfaber@victronenergy.com",
"desc": "Get solar forecasting per location",
"keywords": "solar,forecast,api",
"license": "GPL-3.0"
},
"color": "#FFCC66",
"inputLabels": [
"trigger"
],
"outputLabels": [
"output",
"graph",
""
],
"icon": "font-awesome/fa-sun-o",
"status": {
"x": 680,
"y": 560,
"wires": [
{
"id": "1bfc1cde3ee94e4b",
"port": 0
},
{
"id": "a798fbe66cf133d5",
"port": 0
}
]
}
},
{
"id": "c706820c0d61f023",
"type": "http request",
"z": "93f485839dfa27a8",
"name": "",
"method": "GET",
"ret": "txt",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 390,
"y": 180,
"wires": [
[
"1b5ccaa05d54f7c3"
]
]
},
{
"id": "b9488734852cd0ca",
"type": "function",
"z": "93f485839dfa27a8",
"name": "create forecast.solar url",
"func": "msg.url = 'https://api.forecast.solar/';\n\nif (env.get('apikey')) {\n msg.url += env.get('apikey') + '/';\n }\n\nmsg.url += env.get('type') + '/';\n\nmsg.url += env.get('watt') + '/';\n\nmsg.url += env.get('latitude') + '/' +\n env.get('longitude') + '/' +\n env.get('declination') + '/' +\n env.get('azimuth') + '/' +\n env.get('modules power');\n\nif (env.get('horizon')) {\n msg.url += '?horizon=' + env.get('horizon')\n}\nmsg.topic = 'solar forecast: '+(env.get('type') || '');\nmsg.topic += (' '+env.get('watt') || '');\nif (env.get('kwhoutput')) {\n msg.topic += ' (kWh)';\n}\n\nreturn msg;",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 630,
"y": 100,
"wires": [
[
"975daf96f15cfb61"
]
]
},
{
"id": "1b5ccaa05d54f7c3",
"type": "json",
"z": "93f485839dfa27a8",
"name": "Convert to json",
"property": "payload",
"action": "",
"pretty": false,
"x": 680,
"y": 180,
"wires": [
[
"e718a22973cc2864"
]
]
},
{
"id": "559391d1288f762a",
"type": "function",
"z": "93f485839dfa27a8",
"name": "update ratelimit",
"func": "var remaining = msg.payload.message.ratelimit.remaining || 0;\nvar limit = msg.payload.message.ratelimit.limit;\n\nflow.set('forecast.solar.ratelimit.remaining', remaining)\nflow.set('forecast.solar.ratelimit.limit', limit)\n\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 520,
"y": 480,
"wires": [
[
"e56826252134b93a"
]
]
},
{
"id": "e718a22973cc2864",
"type": "link out",
"z": "93f485839dfa27a8",
"name": "link out 1",
"mode": "link",
"links": [
"3fa24f2d08195961",
"0a20e852662c8cec"
],
"x": 815,
"y": 180,
"wires": []
},
{
"id": "3fa24f2d08195961",
"type": "link in",
"z": "93f485839dfa27a8",
"name": "link in 1",
"links": [
"e718a22973cc2864"
],
"x": 385,
"y": 480,
"wires": [
[
"559391d1288f762a"
]
]
},
{
"id": "0a20e852662c8cec",
"type": "link in",
"z": "93f485839dfa27a8",
"name": "link in 2",
"links": [
"e718a22973cc2864"
],
"x": 225,
"y": 260,
"wires": [
[
"fcc8d69a3ab88e6d"
]
]
},
{
"id": "4734b6f403e1f03e",
"type": "inject",
"z": "93f485839dfa27a8",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": true,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 530,
"y": 440,
"wires": [
[
"e56826252134b93a"
]
]
},
{
"id": "1bfc1cde3ee94e4b",
"type": "function",
"z": "93f485839dfa27a8",
"name": "update status",
"func": "var remaining = flow.get('forecast.solar.ratelimit.remaining') || -1;\nvar limit = flow.get('forecast.solar.ratelimit.limit') || -1\n\nvar text = remaining.toString() + '/' + limit.toString();\nvar fill = \"green\";\n\nif (remaining == 0) {\n fill = \"red\";\n text = \"Limit used\";\n}\n\nif (remaining > 0 && remaining < limit / 2) {\n fill = \"yellow\"\n}\n\nif (remaining == -1 ) {\n fill = \"blue\"\n text = \"Limits unknown\"\n}\n\nmsg.payload = ({ fill: fill, text: text });\n\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 520,
"y": 580,
"wires": [
[]
]
},
{
"id": "a18e96179ec2d987",
"type": "function",
"z": "93f485839dfa27a8",
"name": "Create graph output",
"func": "var m = {};\nm.labels = [];\nm.data = [];\nm.series = [];\n\nfor (let j = 0; j <= msg.days; j++) {\n m.data[j] = [];\n}\n\nif (msg.watt === 'watt_hours_day' || msg.watt === 'watthours/day') {\n var i = 0;\n if (msg.kwhoutput) {\n m.series.push(\"kWh per day\");\n } else {\n m.series.push(\"Watt hours per day\");\n }\n for (const key in msg.payload.result) {\n m.labels.push(key);\n if (msg.kwhoutput) {\n m.data[i] = +(Math.round(msg.payload.result[key]/100)*.1).toFixed(1);\n } else {\n m.data[i] = msg.payload.result[key];\n }\n i++;\n }\n m.data = [m.data];\n return { payload: [m] };\n}\n\nfor (let i = 0; i <= 23; i++) {\n\n m.labels.push(i.toString()+':00');\n if (msg.resolution === 4) {\n m.labels.push(i.toString()+':15');\n }\n if (msg.resolution === 2 || msg.resolution == 4) {\n m.labels.push(i.toString()+':30');\n }\n if (msg.resolution === 4) {\n m.labels.push(i.toString()+':45');\n }\n\n for (let j = 0; j <= msg.days; j++) {\n m.data[j].push(0);\n if (msg.resolution === 4) {\n m.data[j].push(0)\n }\n if (msg.resolution === 2 || msg.resolution == 4) {\n m.data[j].push(0)\n }\n if (msg.resolution === 4) {\n m.data[j].push(0)\n }\n\n }\n}\n\nvar offset = 0;\nfor (const key in msg.payload.result) {\n var d = new Date(key)\n if (m.series.indexOf(d.toISOString().split('T')[0]) === -1) {\n m.series.push(d.toISOString().split('T')[0])\n }\n\n var h = d.getHours();\n var minutes = d.getMinutes();\n\n if (minutes === 0 ) {\n offset = 0;\n } else {\n offset++;\n }\n\n if (msg.kwhoutput) {\n m.data[m.series.length - 1][h*msg.resolution+offset] = +(Math.round(msg.payload.result[key]/100)*.1).toFixed(1);\n } else {\n m.data[m.series.length - 1][h*msg.resolution+offset] = msg.payload.result[key];\n }\n}\n\nif (msg.watt === 'watt_hours') {\n for (const i in m.data) {\n let x = m.data[i][0]\n for (const d in m.data[i]) {\n if ( x > m.data[i][d]) {\n m.data[i][d] = x \n }\n x = m.data[i][d]\n }\n }\n}\n\nreturn { payload: [m] };\n",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 360,
"y": 340,
"wires": [
[
"5b0a430fb61e70e7"
]
]
},
{
"id": "975daf96f15cfb61",
"type": "link out",
"z": "93f485839dfa27a8",
"name": "link out 7",
"mode": "link",
"links": [
"14f2e68e572f4ef8"
],
"x": 805,
"y": 100,
"wires": []
},
{
"id": "14f2e68e572f4ef8",
"type": "link in",
"z": "93f485839dfa27a8",
"name": "link in 18",
"links": [
"975daf96f15cfb61"
],
"x": 245,
"y": 180,
"wires": [
[
"c706820c0d61f023"
]
]
},
{
"id": "c4307905e114824f",
"type": "catch",
"z": "93f485839dfa27a8",
"name": "",
"scope": null,
"uncaught": false,
"x": 260,
"y": 440,
"wires": [
[
"f427f19392c399ce"
]
]
},
{
"id": "e56826252134b93a",
"type": "link out",
"z": "93f485839dfa27a8",
"name": "link out 8",
"mode": "link",
"links": [
"dbaf8f5f5a920686"
],
"x": 685,
"y": 480,
"wires": []
},
{
"id": "dbaf8f5f5a920686",
"type": "link in",
"z": "93f485839dfa27a8",
"name": "link in 19",
"links": [
"e56826252134b93a"
],
"x": 385,
"y": 580,
"wires": [
[
"1bfc1cde3ee94e4b"
]
]
},
{
"id": "f427f19392c399ce",
"type": "link out",
"z": "93f485839dfa27a8",
"name": "link out 9",
"mode": "link",
"links": [
"2ded0c14a222b4d9",
"2f42837904c91d73"
],
"x": 375,
"y": 440,
"wires": []
},
{
"id": "2ded0c14a222b4d9",
"type": "link in",
"z": "93f485839dfa27a8",
"name": "link in 20",
"links": [
"f427f19392c399ce"
],
"x": 385,
"y": 540,
"wires": [
[
"a798fbe66cf133d5"
]
]
},
{
"id": "a798fbe66cf133d5",
"type": "function",
"z": "93f485839dfa27a8",
"name": "Set error status",
"func": "node.warn(msg.payload)\nmsg.payload = ({ fill: \"red\", text: msg.payload });\n\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 520,
"y": 540,
"wires": [
[]
]
},
{
"id": "2f42837904c91d73",
"type": "link in",
"z": "93f485839dfa27a8",
"name": "link in 21",
"links": [
"f427f19392c399ce"
],
"x": 665,
"y": 280,
"wires": [
[]
]
},
{
"id": "fcc8d69a3ab88e6d",
"type": "function",
"z": "93f485839dfa27a8",
"name": "Processed info",
"func": "msg.resolution = 60;\nmsg.days = 1;\nmsg.type = env.get('type');\nmsg.watt = env.get('watt');\nmsg.kwhoutput = env.get('kwhoutput');\n\nvar key1 = Object.keys(msg.payload.result)[1];\nvar key2 = Object.keys(msg.payload.result)[2];\nvar key3 = Object.keys(msg.payload.result)[Object.keys(msg.payload.result).length-1];\n\nvar d1 = new Date(key1);\nvar d2 = new Date(key2); \nvar d3 = new Date(key3);\nmsg.resolution = 3600000 / (d2.getTime() - d1.getTime());\n\nmsg.days = Math.floor((d3.getTime() - d1.getTime()) / (1000 * 3600 * 24));\n\nif (msg.watt === 'watt_hours_day' || msg.watt === 'watthours/day') {\n msg.resolution = null;\n}\n\nreturn msg;",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 360,
"y": 260,
"wires": [
[
"a18e96179ec2d987"
]
],
"info": "Function to process the result from forecast.solar to add\nextra information, which is handy for either graphing or\nto store in a database.\n\n\nThe extra values added:\n- `msg.resolution` - The number of measurements per hour. If\nno API key is used, this will be 1. Other values may be 2 or 4.\n- `msg.days` - The number of days in the forcast. If no API\n- key is used this will be 1. Other values may be 3 or 6."
},
{
"id": "5b0a430fb61e70e7",
"type": "function",
"z": "93f485839dfa27a8",
"name": "Filter graph",
"func": "\nif (env.get('showday')) {\n const weekday = [\"Sunday\", \"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\", \"Saturday\"];\n msg.payload[0].labels.forEach(function (/** @type {string | number | Date} */ date, /** @type {string | number} */ index, /** @type {{ [x: string]: string; }} */ array) {\n const d = new Date(date)\n if (!isNaN(d)) {\n array[index] = weekday[d.getDay()]\n }\n })\n msg.payload[0].series.forEach(function (/** @type {string | number | Date} */ date, /** @type {string | number} */ index, /** @type {{ [x: string]: string; }} */ array) {\n const d = new Date(date)\n if (!isNaN(d)) {\n array[index] = weekday[d.getDay()]\n }\n })\n}\n\nif (env.get('watt') === 'watt_hours_day' || env.get('watt') === 'watthours/day') {\n if (!env.get('showtoday')) {\n msg.payload[0].data[0].shift();\n msg.payload[0].labels.shift();\n }\n return msg;\n}\n\nif (!env.get('showtoday')) {\n msg.payload[0].data.shift();\n msg.payload[0].series.shift();\n}\n\nvar forecasted = msg.payload[0].series.length;\n\nif ((Number(env.get('daystoforecast')) > -1) && (Number(env.get('daystoforecast')) < forecasted)) {\n for (let i = 1; i <= (forecasted - Number(env.get('daystoforecast'))); i++ ) {\n msg.payload[0].data.pop();\n msg.payload[0].series.pop();\n }\n}\n\nif (env.get('widengraph')) {\n var c = msg.payload[0].labels.length;\n var x = 0;\n for (let i = 0; i < c; i++) {\n var remove = true;\n for (let d = 0; d < msg.payload[0].data.length; d++) {\n if (msg.payload[0].data[d][x] > 0) {\n remove = false;\n }\n }\n if (remove) {\n msg.payload[0].labels.splice(x, 1);\n for (let d = 0; d < msg.payload[0].data.length; d++) {\n msg.payload[0].data[d].splice(x, 1);\n }\n x--;\n }\n x++;\n }\n // Still the first and last datapoints should be zero, so\n // add those again\n msg.payload[0].labels.unshift('');\n msg.payload[0].labels.push('');\n for (let d = 0; d < msg.payload[0].data.length; d++) {\n msg.payload[0].data[d].unshift(0);\n msg.payload[0].data[d].push(0);\n } \n}\n\nreturn msg;",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 590,
"y": 340,
"wires": [
[]
]
},
{
"id": "c8dc6aa14b9f3e92",
"type": "delay",
"z": "93f485839dfa27a8",
"name": "1 msg/15 minutes",
"pauseType": "rate",
"timeout": "5",
"timeoutUnits": "seconds",
"rate": "1",
"nbRateUnits": "15",
"rateUnits": "minute",
"randomFirst": "1",
"randomLast": "5",
"randomUnits": "seconds",
"drop": false,
"allowrate": false,
"outputs": 1,
"x": 390,
"y": 100,
"wires": [
[
"b9488734852cd0ca"
]
]
},
{
"id": "5fdbadbaa356f79d",
"type": "subflow:93f485839dfa27a8",
"z": "d378cf57268ffe49",
"name": "East - Watt hours (energy) summarized for each day",
"env": [
{
"name": "declination",
"value": "15",
"type": "num"
},
{
"name": "azimuth",
"value": "-90",
"type": "num"
},
{
"name": "modules power",
"value": "7.830",
"type": "num"
},
{
"name": "apikey",
"type": "cred"
},
{
"name": "watt",
"value": "watthours/day",
"type": "str"
},
{
"name": "kwhoutput",
"type": "bool",
"value": "true"
},
{
"name": "showday",
"type": "bool",
"value": "true"
}
],
"x": 520,
"y": 60,
"wires": [
[],
[
"595c17ba9e21e1af",
"f491f6591a2cc87a"
]
]
}
]
@dirkjanfaber
Copy link
Author

This is a really nice tool. thanks for creating. I did notice on the graph output for 'Watt hours (energy) summarised over the day' that where there is no data from forecast solar the entry on the graph is 0. This means you get an increasing graph throughout the day and then it suddenly drops to 0. It would be better if it stayed at the previous total and the graph line was horizontal.

I usually check the "widen graph" so that does not happen. But I've released an update fixing this if that option is not checked. Thanks for the feedback!

@madeinoz67
Copy link

This is great thankyou!

I have it successfully pulling down estimates for my location, however Ive noticed the the graph output is not working, If I put a debug node on the graph output there is no results, results only occurring on output 1

@dirkjanfaber
Copy link
Author

This is great thankyou!

I have it successfully pulling down estimates for my location, however Ive noticed the the graph output is not working, If I put a debug node on the graph output there is no results, results only occurring on output 1

I need a bit more info in order to debug it. Can you provide your configuration, so I can take a look at it?

@madeinoz67
Copy link

madeinoz67 commented Dec 28, 2023

I need a bit more info in order to debug it. Can you provide your configuration, so I can take a look at it?

I'm just using the example posted on the page for the dual array, except with geolocation changed to suit and API key added. Ive also been trying to debug so have added some more debug nodes

Click me
[
    {
        "id": "036fa37c8eb5f440",
        "type": "subflow",
        "name": "Solar forecast",
        "info": "This subflow uses the http request node to fetch solar forecasts for geographical positions, using the API from https://forecast.solar/. Please check their website and consider getting a paid account.\n\nDo note that, on a free account, you are limited in the number of requests to do. Also note that the data only gets updated once every 15 minutes, so there is no reason to query more often. There is rate limiting built in the subflow not to perform requests more than once every 15 minutes.\n\n# Configuration\n\nIt uses the parameters as described on: http://doc.forecast.solar/doku.php\n\n - `:apikey` - personal API key for registered users\n - `:lat` - latitude of location, -90 (south) … 90 (north); Internal precission is 0.0001 or abt. 10 m\n - `:lon` - longitude of location, -180 (west) … 180 (east); Internal precission is 0.0001 or abt. 10 m\n - `:dec` - plane declination, 0 (horizontal) … 90 (vertical); Internal precission is integer\n - `:az` - plane azimuth, -180 … 180 (-180 = north, -90 = east, 0 = south, 90 = west, 180 = north); Internal precission is integer\n - `:kwp` - installed modules power in kilo watt peak (kWp)\n\nYou can choose between 3 different type of requests. Note that only `estimate` is available on the free plan.\n\n- `estimate` - this is the forecasted estimate that your panels should produce (given the right parameter settings)\n- `history` - historycal data\n- `clear sky` - estimate given if there would be a clear sky tomorrow\n\nIn case of estimates, one of the following options can be selected:\n- `watts` - Watts (power) average for the period\n- `watt_hours_period` - Watt hours (energy) for the period\n- `watt_hours` - Watt hours (energy) summarized over the day\n- `watt_hours_day` - Watt hours (energy) summarized for each day\n\nFor the graph output there are some extra settings available:\n\n- _Output in kWh_ - when checked output can be set to kWh instead of Wh\n- _Show todays forecast_ - whether or not to include todays forecast\n- _Days to forecast_ - the number of days to forecast (excluding today). Note that you can not get more days forecasted than your API key allows.\n- _Widen graph_ - widen the graph to only show non-zero values\n- _Show day instead of date_ - Show the day instead of the date in the series and labels\n\n# Input \n\nThe input is for triggering the solar forecast request. \nIt triggers when injecting a message into the node.\n\n# Output\n\nThere are two outputs. The first output is an object with the result and a status message stored into the `msg.payload`.\n\nMost important is the `msg.payload.result`, which contains the estimated production of the panels. E.g.:\n\n```\npayload: object\n  result: object\n    2022-11-28: 23\n    2022-11-29: 35\n```\n\nThe `msg.payload.message` gives information on how successful the query was, the exitcode of the query and the status of the rate limit (how many queries you have left).\n\nThe **second** output can be directly linked to a line or a bar chart, quickly giving a once-glance overview for the predicted forecast.\n\n# Status\n\nInitially the status of the note will be a blue dot, showing \"_Unknown limit_\", as it is unaware of the set ratelmits. After the first request, the returned ratelimit will be put in the text in the form of `remaining/limit`. If more than half the limit is remaining, the dot will be green. If less then half the limit is remaining, the dot will be yellow. If no limit is left, the dot will turn red.\nPlease keep in mind that the ratelimit will be reset after one hour, so you can send a new request after that hour.\n\nIf something is wrong in the API request, the dot will turn red\nand the message will contain the msg.payload with the error. This\nhappens typically when the API is temporally down for maintenance.",
        "category": "",
        "in": [
            {
                "x": 240,
                "y": 100,
                "wires": [
                    {
                        "id": "2dd98e18878e7685"
                    }
                ]
            }
        ],
        "out": [
            {
                "x": 760,
                "y": 260,
                "wires": [
                    {
                        "id": "8a91d3b9d653d552",
                        "port": 0
                    },
                    {
                        "id": "d7cf21b59fb01541",
                        "port": 0
                    }
                ]
            },
            {
                "x": 770,
                "y": 340,
                "wires": [
                    {
                        "id": "84fe53a48efc7294",
                        "port": 0
                    }
                ]
            }
        ],
        "env": [
            {
                "name": "latitude",
                "type": "num",
                "value": "-33.33369",
                "ui": {
                    "icon": "font-awesome/fa-location-arrow",
                    "label": {
                        "en-US": "Latitude"
                    },
                    "type": "input",
                    "opts": {
                        "types": [
                            "num"
                        ]
                    }
                }
            },
            {
                "name": "longitude",
                "type": "num",
                "value": "115.63959",
                "ui": {
                    "icon": "font-awesome/fa-location-arrow",
                    "label": {
                        "en-US": "Longitude"
                    },
                    "type": "input",
                    "opts": {
                        "types": [
                            "num"
                        ]
                    }
                }
            },
            {
                "name": "declination",
                "type": "num",
                "value": "22",
                "ui": {
                    "icon": "font-awesome/fa-chevron-up",
                    "label": {
                        "en-US": "Declination"
                    },
                    "type": "input",
                    "opts": {
                        "types": [
                            "num"
                        ]
                    }
                }
            },
            {
                "name": "azimuth",
                "type": "num",
                "value": "0",
                "ui": {
                    "icon": "font-awesome/fa-compass",
                    "label": {
                        "en-US": "Azimuth"
                    },
                    "type": "spinner",
                    "opts": {
                        "min": -180,
                        "max": 180
                    }
                }
            },
            {
                "name": "modules power",
                "type": "num",
                "value": "7.125",
                "ui": {
                    "icon": "font-awesome/fa-power-off",
                    "label": {
                        "en-US": "Modules power (kWp)"
                    },
                    "type": "input",
                    "opts": {
                        "types": [
                            "num"
                        ]
                    }
                }
            },
            {
                "name": "apikey",
                "type": "cred",
                "ui": {
                    "icon": "font-awesome/fa-key",
                    "label": {
                        "en-US": "API key"
                    },
                    "type": "input",
                    "opts": {
                        "types": [
                            "cred"
                        ]
                    }
                }
            },
            {
                "name": "type",
                "type": "str",
                "value": "estimate",
                "ui": {
                    "label": {
                        "en-US": "Type"
                    },
                    "type": "select",
                    "opts": {
                        "opts": [
                            {
                                "l": {
                                    "en-US": "Estimate"
                                },
                                "v": "estimate"
                            },
                            {
                                "l": {
                                    "en-US": "History"
                                },
                                "v": "history"
                            },
                            {
                                "l": {
                                    "en-US": "Clear sky"
                                },
                                "v": "clearsky"
                            }
                        ]
                    }
                }
            },
            {
                "name": "watt",
                "type": "str",
                "value": "watts",
                "ui": {
                    "icon": "font-awesome/fa-question-circle-o",
                    "label": {
                        "en-US": "Watt"
                    },
                    "type": "select",
                    "opts": {
                        "opts": [
                            {
                                "l": {
                                    "en-US": "Watts (power) average for the period"
                                },
                                "v": "watts"
                            },
                            {
                                "l": {
                                    "en-US": "Watt hours (energy) for the period"
                                },
                                "v": "watt_hours_period"
                            },
                            {
                                "l": {
                                    "en-US": "Watt hours (energy) summarized over the day"
                                },
                                "v": "watt_hours"
                            },
                            {
                                "l": {
                                    "en-US": "Watt hours (energy) summarized for each day"
                                },
                                "v": "watt_hours_day"
                            }
                        ]
                    }
                }
            },
            {
                "name": "kwhoutput",
                "type": "bool",
                "value": "false",
                "ui": {
                    "label": {
                        "en-US": "Output in kWh (in the graph)"
                    },
                    "type": "checkbox"
                }
            },
            {
                "name": "showtoday",
                "type": "bool",
                "value": "true",
                "ui": {
                    "label": {
                        "en-US": "Show todays forecast"
                    },
                    "type": "checkbox"
                }
            },
            {
                "name": "daystoforecast",
                "type": "str",
                "value": "0",
                "ui": {
                    "label": {
                        "en-US": "Days to forecast"
                    },
                    "type": "select",
                    "opts": {
                        "opts": [
                            {
                                "l": {
                                    "en-US": "Max"
                                },
                                "v": "-1"
                            },
                            {
                                "l": {
                                    "en-US": "0"
                                },
                                "v": "0"
                            },
                            {
                                "l": {
                                    "en-US": "1"
                                },
                                "v": "1"
                            },
                            {
                                "l": {
                                    "en-US": "2"
                                },
                                "v": "2"
                            },
                            {
                                "l": {
                                    "en-US": "3"
                                },
                                "v": "3"
                            },
                            {
                                "l": {
                                    "en-US": "4"
                                },
                                "v": "4"
                            },
                            {
                                "l": {
                                    "en-US": "5"
                                },
                                "v": "5"
                            },
                            {
                                "l": {
                                    "en-US": "6"
                                },
                                "v": "6"
                            }
                        ]
                    }
                }
            },
            {
                "name": "widengraph",
                "type": "bool",
                "value": "true",
                "ui": {
                    "label": {
                        "en-US": "Widen graph"
                    },
                    "type": "checkbox"
                }
            },
            {
                "name": "showday",
                "type": "bool",
                "value": "false",
                "ui": {
                    "label": {
                        "en-US": "Show day instead of date"
                    },
                    "type": "checkbox"
                }
            }
        ],
        "meta": {
            "module": "Solar Forecast",
            "version": "0.0.10",
            "author": "dfaber@victronenergy.com",
            "desc": "Get solar forecasting per location",
            "keywords": "solar,forecast,api",
            "license": "GPL-3.0"
        },
        "color": "#FFCC66",
        "inputLabels": [
            "trigger"
        ],
        "outputLabels": [
            "output",
            "graph"
        ],
        "icon": "font-awesome/fa-sun-o",
        "status": {
            "x": 680,
            "y": 560,
            "wires": [
                {
                    "id": "573b4967ebaa454e",
                    "port": 0
                },
                {
                    "id": "bb9975ae289fc6fe",
                    "port": 0
                }
            ]
        }
    },
    {
        "id": "fe4bbb79d1a9a155",
        "type": "http request",
        "z": "036fa37c8eb5f440",
        "name": "",
        "method": "GET",
        "ret": "txt",
        "paytoqs": "ignore",
        "url": "",
        "tls": "",
        "persist": false,
        "proxy": "",
        "insecureHTTPParser": false,
        "authType": "",
        "senderr": false,
        "headers": [],
        "x": 390,
        "y": 180,
        "wires": [
            [
                "7ef23e357018643e",
                "8da3b5c098994451"
            ]
        ]
    },
    {
        "id": "f3f1fb8ea6070183",
        "type": "function",
        "z": "036fa37c8eb5f440",
        "name": "create forecast.solar url",
        "func": "msg.url = 'https://api.forecast.solar/';\n\nif (env.get('apikey')) {\n    msg.url += env.get('apikey') + '/';\n    }\n\nmsg.url += env.get('type') + '/';\n\nmsg.url += env.get('watt') + '/';\n\nmsg.url += env.get('latitude') + '/' +\n           env.get('longitude') + '/' +\n           env.get('declination') + '/' +\n           env.get('azimuth') + '/' +\n           env.get('modules power');\n\nmsg.topic = 'solar forecast: '+(env.get('type') || '');\nmsg.topic += (' '+env.get('watt') || '');\nif (env.get('kwhoutput')) {\n    msg.topic += ' (kWh)';\n}\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 630,
        "y": 100,
        "wires": [
            [
                "acb72ffd59ec2682"
            ]
        ]
    },
    {
        "id": "7ef23e357018643e",
        "type": "json",
        "z": "036fa37c8eb5f440",
        "name": "Convert to json",
        "property": "payload",
        "action": "",
        "pretty": false,
        "x": 680,
        "y": 180,
        "wires": [
            [
                "ba8a174a8905900a"
            ]
        ]
    },
    {
        "id": "7982e210537c2b5c",
        "type": "function",
        "z": "036fa37c8eb5f440",
        "name": "update ratelimit",
        "func": "var remaining = msg.payload.message.ratelimit.remaining || 0;\nvar limit = msg.payload.message.ratelimit.limit;\n\nflow.set('forecast.solar.ratelimit.remaining', remaining)\nflow.set('forecast.solar.ratelimit.limit', limit)\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 520,
        "y": 480,
        "wires": [
            [
                "4ca47d85e9f13fb3"
            ]
        ]
    },
    {
        "id": "ba8a174a8905900a",
        "type": "link out",
        "z": "036fa37c8eb5f440",
        "name": "link out 1",
        "mode": "link",
        "links": [
            "f18df797013dbda2",
            "85c219c42c34552a"
        ],
        "x": 815,
        "y": 180,
        "wires": []
    },
    {
        "id": "f18df797013dbda2",
        "type": "link in",
        "z": "036fa37c8eb5f440",
        "name": "link in 1",
        "links": [
            "ba8a174a8905900a"
        ],
        "x": 385,
        "y": 480,
        "wires": [
            [
                "7982e210537c2b5c"
            ]
        ]
    },
    {
        "id": "85c219c42c34552a",
        "type": "link in",
        "z": "036fa37c8eb5f440",
        "name": "link in 2",
        "links": [
            "ba8a174a8905900a"
        ],
        "x": 225,
        "y": 260,
        "wires": [
            [
                "d7cf21b59fb01541",
                "c41f82959c5067db"
            ]
        ]
    },
    {
        "id": "7388fdfde570d53d",
        "type": "inject",
        "z": "036fa37c8eb5f440",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": true,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 530,
        "y": 440,
        "wires": [
            [
                "4ca47d85e9f13fb3"
            ]
        ]
    },
    {
        "id": "2dd98e18878e7685",
        "type": "delay",
        "z": "036fa37c8eb5f440",
        "name": "1 msg/15 minutes",
        "pauseType": "rate",
        "timeout": "5",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "15",
        "rateUnits": "minute",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": true,
        "allowrate": false,
        "outputs": 1,
        "x": 390,
        "y": 100,
        "wires": [
            [
                "f3f1fb8ea6070183"
            ]
        ]
    },
    {
        "id": "573b4967ebaa454e",
        "type": "function",
        "z": "036fa37c8eb5f440",
        "name": "update status",
        "func": "var remaining = flow.get('forecast.solar.ratelimit.remaining') || -1;\nvar limit = flow.get('forecast.solar.ratelimit.limit') || -1\n\nvar text = remaining.toString() + '/' + limit.toString();\nvar fill = \"green\";\n\nif (remaining == 0) {\n    fill = \"red\";\n    text = \"Limit used\";\n}\n\nif (remaining > 0 && remaining < limit / 2) {\n    fill = \"yellow\"\n}\n\nif (remaining == -1 ) {\n    fill = \"blue\"\n    text = \"Limits unknown\"\n}\n\nmsg.payload = ({ fill: fill, text: text });\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 520,
        "y": 580,
        "wires": [
            []
        ]
    },
    {
        "id": "6363f13d44ebba17",
        "type": "function",
        "z": "036fa37c8eb5f440",
        "name": "Create graph output",
        "func": "var m = {};\nm.labels = [];\nm.data = [];\n\nm.series = [];\nm.data = [];\nm.labels = [];\n\nfor (let j = 0; j <= msg.days; j++) {\n    m.data[j] = [];\n}\n\nif (msg.watt === 'watt_hours_day') {\n    var i = 0;\n    if (msg.kwhoutput) {\n        m.series.push(\"kWh per day\");\n    } else {\n        m.series.push(\"Watt hours per day\");\n    }\n    for (const key in msg.payload.result) {\n        m.labels.push(key);\n        if (msg.kwhoutput) {\n            m.data[i] = +(Math.round(msg.payload.result[key]/100)*.1).toFixed(1);\n        } else {\n            m.data[i] = msg.payload.result[key];\n        }\n        i++;\n    }\n    m.data = [m.data];\n    return { payload: [m] };\n}\n\nfor (let i = 0; i <= 23; i++) {\n\n    m.labels.push(i.toString()+':00');\n    if (msg.resolution === 4) {\n       m.labels.push(i.toString()+':15');\n    }\n    if (msg.resolution === 2 || msg.resolution == 4) {\n       m.labels.push(i.toString()+':30');\n    }\n    if (msg.resolution === 4) {\n       m.labels.push(i.toString()+':45');\n    }\n\n    for (let j = 0; j <= msg.days; j++) {\n        m.data[j].push(0);\n        if (msg.resolution === 4) {\n           m.data[j].push(0)\n        }\n        if (msg.resolution === 2 || msg.resolution == 4) {\n           m.data[j].push(0)\n        }\n        if (msg.resolution === 4) {\n           m.data[j].push(0)\n        }\n\n    }\n}\n\nvar offset = 0;\nfor (const key in msg.payload.result) {\n    var d = new Date(key)\n    if (m.series.indexOf(d.toISOString().split('T')[0]) === -1) {\n        m.series.push(d.toISOString().split('T')[0])\n    }\n\n    var h = d.getHours();\n    var minutes = d.getMinutes();\n\n    if (minutes === 0 ) {\n        offset = 0;\n    } else {\n        offset++;\n    }\n\n    if (msg.kwhoutput) {\n        m.data[m.series.length - 1][h*msg.resolution+offset] = +(Math.round(msg.payload.result[key]/100)*.1).toFixed(1);\n    } else {\n        m.data[m.series.length - 1][h*msg.resolution+offset] = msg.payload.result[key];\n    }\n}\n\nreturn { payload: [m] };\n",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 360,
        "y": 340,
        "wires": [
            [
                "84fe53a48efc7294",
                "34216c7b697900df"
            ]
        ]
    },
    {
        "id": "acb72ffd59ec2682",
        "type": "link out",
        "z": "036fa37c8eb5f440",
        "name": "link out 7",
        "mode": "link",
        "links": [
            "8888105f14cc332c"
        ],
        "x": 805,
        "y": 100,
        "wires": []
    },
    {
        "id": "8888105f14cc332c",
        "type": "link in",
        "z": "036fa37c8eb5f440",
        "name": "link in 18",
        "links": [
            "acb72ffd59ec2682"
        ],
        "x": 245,
        "y": 180,
        "wires": [
            [
                "fe4bbb79d1a9a155"
            ]
        ]
    },
    {
        "id": "1b85f8934b39b995",
        "type": "catch",
        "z": "036fa37c8eb5f440",
        "name": "",
        "scope": null,
        "uncaught": false,
        "x": 260,
        "y": 440,
        "wires": [
            [
                "fd93234f510727ee"
            ]
        ]
    },
    {
        "id": "4ca47d85e9f13fb3",
        "type": "link out",
        "z": "036fa37c8eb5f440",
        "name": "link out 8",
        "mode": "link",
        "links": [
            "c8b9e95b9c69d350"
        ],
        "x": 685,
        "y": 480,
        "wires": []
    },
    {
        "id": "c8b9e95b9c69d350",
        "type": "link in",
        "z": "036fa37c8eb5f440",
        "name": "link in 19",
        "links": [
            "4ca47d85e9f13fb3"
        ],
        "x": 385,
        "y": 580,
        "wires": [
            [
                "573b4967ebaa454e"
            ]
        ]
    },
    {
        "id": "fd93234f510727ee",
        "type": "link out",
        "z": "036fa37c8eb5f440",
        "name": "link out 9",
        "mode": "link",
        "links": [
            "036240bfd051d8c8",
            "8a91d3b9d653d552"
        ],
        "x": 375,
        "y": 440,
        "wires": []
    },
    {
        "id": "036240bfd051d8c8",
        "type": "link in",
        "z": "036fa37c8eb5f440",
        "name": "link in 20",
        "links": [
            "fd93234f510727ee"
        ],
        "x": 385,
        "y": 540,
        "wires": [
            [
                "bb9975ae289fc6fe"
            ]
        ]
    },
    {
        "id": "bb9975ae289fc6fe",
        "type": "function",
        "z": "036fa37c8eb5f440",
        "name": "Set error status",
        "func": "node.warn(msg.payload)\nmsg.payload = ({ fill: \"red\", text: msg.payload });\n\nreturn msg;",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 520,
        "y": 540,
        "wires": [
            []
        ]
    },
    {
        "id": "8a91d3b9d653d552",
        "type": "link in",
        "z": "036fa37c8eb5f440",
        "name": "link in 21",
        "links": [
            "fd93234f510727ee"
        ],
        "x": 665,
        "y": 280,
        "wires": [
            []
        ]
    },
    {
        "id": "d7cf21b59fb01541",
        "type": "function",
        "z": "036fa37c8eb5f440",
        "name": "Processed info",
        "func": "msg.resolution = 60;\nmsg.days = 1;\nmsg.type = env.get('type');\nmsg.watt = env.get('watt');\nmsg.kwhoutput = env.get('kwhoutput');\n\nvar key1 = Object.keys(msg.payload.result)[1];\nvar key2 = Object.keys(msg.payload.result)[2];\nvar key3 = Object.keys(msg.payload.result)[Object.keys(msg.payload.result).length-1];\n\nvar d1 = new Date(key1);\nvar d2 = new Date(key2); \nvar d3 = new Date(key3);\nmsg.resolution = 3600000 / (d2.getTime() - d1.getTime());\n\nmsg.days = Math.floor((d3.getTime() - d1.getTime()) / (1000 * 3600 * 24));\n\nif (msg.watt === 'watt_hours_day') {\n    msg.resolution = null;\n}\n\nreturn msg;",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 360,
        "y": 260,
        "wires": [
            [
                "6363f13d44ebba17"
            ]
        ],
        "info": "Function to process the result from forecast.solar to add\nextra information, which is handy for either graphing or\nto store in a database.\n\n\nThe extra values added:\n- `msg.resolution` - The number of measurements per hour. If\nno API key is used, this will be 1. Other values may be 2 or 4.\n- `msg.days` - The number of days in the forcast. If no API\n- key is used this will be 1. Other values may be 3 or 6."
    },
    {
        "id": "84fe53a48efc7294",
        "type": "function",
        "z": "036fa37c8eb5f440",
        "name": "Filter graph",
        "func": "var forecasted = msg.payload[0].series.length;\n\nif ((Number(env.get('daystoforecast')) > -1) && (Number(env.get('daystoforecast')) < forecasted)) {\n    for (let i = 1; i <= (forecasted - Number(env.get('daystoforecast'))); i++ ) {\n        msg.payload[0].data.pop();\n        msg.payload[0].series.pop();\n    }\n}\n\nif (!env.get('showtoday')) {\n    msg.payload[0].data.shift();\n    msg.payload[0].series.shift();\n}\n\nif (env.get('showday')) {\n    const weekday = [\"Sunday\", \"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\", \"Saturday\"];\n    msg.payload[0].labels.forEach(function (/** @type {string | number | Date} */ date, /** @type {string | number} */ index, /** @type {{ [x: string]: string; }} */ array) {\n        const d = new Date(date)\n        if (!isNaN(d)) {\n            array[index] = weekday[d.getDay()]\n        }\n    })\n    msg.payload[0].series.forEach(function (/** @type {string | number | Date} */ date, /** @type {string | number} */ index, /** @type {{ [x: string]: string; }} */ array) {\n        const d = new Date(date)\n        if (!isNaN(d)) {\n            array[index] = weekday[d.getDay()]\n        }\n    })\n}\n\nif (env.get('widengraph')) {\n    var c = msg.payload[0].labels.length;\n    var x = 0;\n    for (let i = 0; i < c; i++) {\n        var remove = true;\n        for (let d = 0; d < msg.payload[0].data.length; d++) {\n            if (msg.payload[0].data[d][x] > 0) {\n                remove = false;\n            }\n        }\n        if (remove) {\n            msg.payload[0].labels.splice(x, 1);\n            for (let d = 0; d < msg.payload[0].data.length; d++) {\n                 msg.payload[0].data[d].splice(x, 1);\n            }\n            x--;\n        }\n        x++;\n    }\n    // Still the first and last datapoints should be zero, so\n    // add those again\n    msg.payload[0].labels.unshift('');\n    msg.payload[0].labels.push('');\n    for (let d = 0; d < msg.payload[0].data.length; d++) {\n         msg.payload[0].data[d].unshift(0);\n         msg.payload[0].data[d].push(0);\n    }   \n}\n\n// Add a msg topic\nmsg.topic = node.name || 'todo'\n\nreturn msg;",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 590,
        "y": 340,
        "wires": [
            []
        ]
    },
    {
        "id": "34216c7b697900df",
        "type": "debug",
        "z": "036fa37c8eb5f440",
        "name": "debug 7",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 530,
        "y": 300,
        "wires": []
    },
    {
        "id": "c41f82959c5067db",
        "type": "debug",
        "z": "036fa37c8eb5f440",
        "name": "debug 8",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 360,
        "y": 220,
        "wires": []
    },
    {
        "id": "8da3b5c098994451",
        "type": "debug",
        "z": "036fa37c8eb5f440",
        "name": "debug 9",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "complete",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 510,
        "y": 140,
        "wires": []
    },
    {
        "id": "1199c6882a5c2a63",
        "type": "tab",
        "label": "Solar",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "f809bdc8dfeffbda",
        "type": "subflow:036fa37c8eb5f440",
        "z": "1199c6882a5c2a63",
        "name": "East",
        "env": [
            {
                "name": "azimuth",
                "value": "-90",
                "type": "num"
            },
            {
                "name": "apikey",
                "type": "cred"
            },
            {
                "name": "daystoforecast",
                "value": "2",
                "type": "str"
            },
            {
                "name": "showday",
                "type": "bool",
                "value": "true"
            }
        ],
        "x": 290,
        "y": 220,
        "wires": [
            [
                "d60b26b35e06f5e3"
            ],
            [
                "a1aab9c83bc4a551",
                "6329a5c28139c6c9",
                "b2c45095f8e1f3a5"
            ]
        ]
    },
    {
        "id": "7ee490177443bfb6",
        "type": "inject",
        "z": "1199c6882a5c2a63",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "900",
        "crontab": "",
        "once": true,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 150,
        "y": 260,
        "wires": [
            [
                "f809bdc8dfeffbda",
                "a5082af6a109aa91"
            ]
        ]
    },
    {
        "id": "a5082af6a109aa91",
        "type": "subflow:036fa37c8eb5f440",
        "z": "1199c6882a5c2a63",
        "name": "West",
        "env": [
            {
                "name": "azimuth",
                "value": "90",
                "type": "num"
            },
            {
                "name": "apikey",
                "type": "cred"
            },
            {
                "name": "daystoforecast",
                "value": "2",
                "type": "str"
            },
            {
                "name": "showday",
                "type": "bool",
                "value": "true"
            }
        ],
        "x": 290,
        "y": 300,
        "wires": [
            [],
            [
                "1e5419fe8e37111b",
                "fc3c997e04976ba9"
            ]
        ]
    },
    {
        "id": "fc3c997e04976ba9",
        "type": "ui_chart",
        "z": "1199c6882a5c2a63",
        "name": "",
        "group": "e0e94dad1ff051d3",
        "order": 8,
        "width": "12",
        "height": "6",
        "label": "West",
        "chartType": "bar",
        "legend": "true",
        "xformat": "HH:mm:ss",
        "interpolate": "linear",
        "nodata": "",
        "dot": false,
        "ymin": "",
        "ymax": "",
        "removeOlder": 1,
        "removeOlderPoints": "",
        "removeOlderUnit": "3600",
        "cutout": 0,
        "useOneColor": true,
        "useUTC": false,
        "colors": [
            "#1f77b4",
            "#aec7e8",
            "#ff7f0e",
            "#2ca02c",
            "#98df8a",
            "#d62728",
            "#ff9896",
            "#9467bd",
            "#c5b0d5"
        ],
        "outputs": 1,
        "useDifferentColor": false,
        "className": "",
        "x": 430,
        "y": 340,
        "wires": [
            []
        ]
    },
    {
        "id": "a1aab9c83bc4a551",
        "type": "ui_chart",
        "z": "1199c6882a5c2a63",
        "name": "",
        "group": "e0e94dad1ff051d3",
        "order": 7,
        "width": "12",
        "height": "6",
        "label": "East",
        "chartType": "bar",
        "legend": "true",
        "xformat": "HH:mm:ss",
        "interpolate": "linear",
        "nodata": "",
        "dot": false,
        "ymin": "",
        "ymax": "",
        "removeOlder": 1,
        "removeOlderPoints": "",
        "removeOlderUnit": "3600",
        "cutout": 0,
        "useOneColor": true,
        "useUTC": false,
        "colors": [
            "#1f77b4",
            "#aec7e8",
            "#ff7f0e",
            "#2ca02c",
            "#98df8a",
            "#d62728",
            "#ff9896",
            "#9467bd",
            "#c5b0d5"
        ],
        "outputs": 1,
        "useDifferentColor": false,
        "className": "",
        "x": 510,
        "y": 160,
        "wires": [
            []
        ]
    },
    {
        "id": "6728854e6b0479ac",
        "type": "ui_chart",
        "z": "1199c6882a5c2a63",
        "name": "",
        "group": "e0e94dad1ff051d3",
        "order": 9,
        "width": "24",
        "height": "8",
        "label": "Combined",
        "chartType": "bar",
        "legend": "true",
        "xformat": "HH:mm:ss",
        "interpolate": "linear",
        "nodata": "",
        "dot": false,
        "ymin": "",
        "ymax": "",
        "removeOlder": 1,
        "removeOlderPoints": "",
        "removeOlderUnit": "3600",
        "cutout": 0,
        "useOneColor": true,
        "useUTC": false,
        "colors": [
            "#1f77b4",
            "#aec7e8",
            "#ff7f0e",
            "#2ca02c",
            "#98df8a",
            "#d62728",
            "#ff9896",
            "#9467bd",
            "#c5b0d5"
        ],
        "outputs": 1,
        "useDifferentColor": false,
        "className": "",
        "x": 1070,
        "y": 220,
        "wires": [
            []
        ]
    },
    {
        "id": "6329a5c28139c6c9",
        "type": "change",
        "z": "1199c6882a5c2a63",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "solar.east",
                "pt": "flow",
                "to": "payload",
                "tot": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 570,
        "y": 280,
        "wires": [
            [
                "4d9efa2d09af8e03",
                "e7bb504717953cc8"
            ]
        ]
    },
    {
        "id": "1e5419fe8e37111b",
        "type": "change",
        "z": "1199c6882a5c2a63",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "solar.west",
                "pt": "flow",
                "to": "payload",
                "tot": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 610,
        "y": 380,
        "wires": [
            [
                "4d9efa2d09af8e03"
            ]
        ]
    },
    {
        "id": "4d9efa2d09af8e03",
        "type": "function",
        "z": "1199c6882a5c2a63",
        "name": "Combine multiple inputs",
        "func": "let solar = flow.get('solar') || []\nif (solar.length === 0) {\n  return;\n}\n\nlet series = []\nlet data = []\nlet labels = []\n\nfor (const key in solar) {\n    for (const s in solar[key][0].series) {\n        series.push(solar[key][0].series[s] + ' ' + key)\n    }\n    for (const d in solar[key][0].data) {\n        data.push(solar[key][0].data[d])\n    }\n    // data.push(solar[key][0].data[0])\n    labels = solar[key][0].labels\n\n}\n\nmsg.payload = [{\n    series,\n    data,\n    labels\n    }]\n\nmsg.ui_control = {\n     options: {\n        scales: {\n            xAxes: [{\n                stacked: true\n            }],\n            yAxes: [{\n                stacked: true\n            }]\n        }\n    }\n}\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 850,
        "y": 280,
        "wires": [
            [
                "6728854e6b0479ac",
                "c82b9207eaa67dd3"
            ]
        ]
    },
    {
        "id": "c82b9207eaa67dd3",
        "type": "debug",
        "z": "1199c6882a5c2a63",
        "name": "debug 3",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 1060,
        "y": 340,
        "wires": []
    },
    {
        "id": "e7bb504717953cc8",
        "type": "debug",
        "z": "1199c6882a5c2a63",
        "name": "debug 4",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 740,
        "y": 180,
        "wires": []
    },
    {
        "id": "b2c45095f8e1f3a5",
        "type": "debug",
        "z": "1199c6882a5c2a63",
        "name": "debug 5",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "complete",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 480,
        "y": 200,
        "wires": []
    },
    {
        "id": "d60b26b35e06f5e3",
        "type": "debug",
        "z": "1199c6882a5c2a63",
        "name": "debug 6",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 500,
        "y": 60,
        "wires": []
    },
    {
        "id": "e0e94dad1ff051d3",
        "type": "ui_group",
        "name": "Visualisations",
        "tab": "e0cd73085d707c9d",
        "order": 5,
        "disp": true,
        "width": "24",
        "collapse": false,
        "className": ""
    },
    {
        "id": "e0cd73085d707c9d",
        "type": "ui_tab",
        "name": "Solar predictions",
        "icon": "dashboard",
        "disabled": false,
        "hidden": false
    }
]

(Edit: Made the code collapsible for readability)

@dirkjanfaber
Copy link
Author

I'm just using the example posted on the page for the dual array, except with geolocation changed to suit and API key added. Ive also been trying to debug so have added some more debug nodes

What I notice is that daystoforeacast is set to 0, which is not that useful. That is both in your code as in my examples. I've updated that. Other than that, I don't directly see the cause.

@fldutch
Copy link

fldutch commented Jan 19, 2024

forecast.solar changed the api request, causing the api to throw an error - Knut from forecast.solar seemed to have deactivated the older api request, that was still available:
e.g. estimate/watt_hours_day/ should now be estimate/watthours/day/
Knut activated this again, but he of course prefers using the new request :)

This is the info on this:
https://doc.forecast.solar/api:estimate#watts_watt_hours_over_day_watt_hours_per_period_watt_hours_per_day

Do you think you are willing to update this (had a short look into the json code, but did not see, if you already implemented this)

@dirkjanfaber
Copy link
Author

Thanks for letting me know @fldutch . I've updated the code to cope with the API changes. It was a minor change.

@fldutch
Copy link

fldutch commented Jan 31, 2024

Hmm, got an error in the status after trying to update to 0.0.12 - rolled back to 0.0.9. Running Venus OS v3.12 at the moment (saying to make shure it's not a node red version issue). Did not try 0.0.11 - tried 0.010 shortly, but also got a warning there.

Also tried deleting the subflow, restart Node Red, install subflow - same error with 0.0.12. Got me crazy :).

Do you have any hints on a best practise to update the subflow?

btw.: I'm not using the graph because data is stored in influxdb and visualized via grafana.

Should switch to Victrons forecast from the api anyway, at the moment i'm still using forecast.solar, solcast and victron to compare the forecasts. Weather really is a pain in the ... making solar forecasts somewhat unreliable here for northern germany ;).

Thx for your great work by the way!

@dirkjanfaber
Copy link
Author

Hmm, got an error in the status after trying to update to 0.0.12 - rolled back to 0.0.9. Running Venus OS v3.12 at the moment (saying to make shure it's not a node red version issue). Did not try 0.0.11 - tried 0.010 shortly, but also got a warning there.

What error did you get exactly? Tried to reproduce, but I did not get any errors.

Also tried deleting the subflow, restart Node Red, install subflow - same error with 0.0.12. Got me crazy :).

Do you have any hints on a best practise to update the subflow?

Node-RED is not that easy with updating subflows. So the usual thing to do would be to delete the old one first and then install the new one.
When I created the subflow, for me it was more of a showcase to show what is possible with a subflow. But it has gotten to the point that, given the complexity of this subflow, it makes more sense to create a regular node instead. But I am currently lacking the time to do so. I do see that this gist also contained an outdated flow-stacked.json file, which I just deleted. Perhaps you copy/pasted that file?

btw.: I'm not using the graph because data is stored in influxdb and visualized via grafana.

Should switch to Victrons forecast from the api anyway, at the moment i'm still using forecast.solar, solcast and victron to compare the forecasts. Weather really is a pain in the ... making solar forecasts somewhat unreliable here for northern germany ;).

The alternative is indeed using the victron-vrm-api for retrieving the solar forecasts.

Thx for your great work by the way!

You are welcome.

@fldutch
Copy link

fldutch commented Feb 6, 2024

Thx4 your reply!

"Both" request status infos just showed red color and "error". Due to this, no new request possible until i deployed random changes to reset the request status counters. Hard to tell. Maybe really an issue with updating the subflow. Deleting the old subflow then importing the new one showed no effect.

So sticking with 0.0.9 is ok for me ;) - i'll have to set up a venus OS for testing purposes to somehow verify this behavior. But switching to victron-vrm-api will be easier i think.

Maybe i'll have to dig deeper into node red to try to completely remove an old subflow via cli (maybe there is still sth. cached - not sure how node red exactly works on that, tried some restart but without any effect)

I also thought i might have copy-pasted the flow-stacked.json - but made sure i did not :).

As long as Knut does not deactivate the deprecated API call again, 0.0.9 should work fine - so no hurry, maybe no hurry at all from a Victron user perspective - your work for Victron is quite more important - still did not have a trial with Dynamic ESS - sticking to my "working" node red setup.

@billouk
Copy link

billouk commented Feb 26, 2024

Hello,
Thanks a lot for your work.

Is there a way to include the "horizon=" parameter ?
https://doc.forecast.solar/horizon

I have a few trees nearby and it would be interesting to take them into account especially with the winter sun being so low.

Greg.

@dirkjanfaber
Copy link
Author

Is there a way to include the "horizon=" parameter ? https://doc.forecast.solar/horizon
I have a few trees nearby and it would be interesting to take them into account especially with the winter sun being so low.

Added the (optional) horizon field to the configuration. In order to update, you will first need to remove the old subflow and re-import the new one.

@Fishbowl1974
Copy link

@dirkjanfaber

Thanks for this brilliant Subflow. I've been using it with Solcast and the VRM APi and averaging the three results to use in a node red Dynamic ESS essentially.

The Forecast.solar subflow has stopped working following node updates. I've uninstalled and reinstalled your current version but I'm just getting a warn error and I can't see why it isn't working. Any chance you could have a quick look at it.

Cheers

James

@dirkjanfaber
Copy link
Author

@Fishbowl1974

Solving the problem starts with being able to reproduce it, so I need some more info.
Any info on used versions and errors you are seeing? Or can you share a screenshot of the configuration?

@Fishbowl1974
Copy link

@Fishbowl1974

Solving the problem starts with being able to reproduce it, so I need some more info. Any info on used versions and errors you are seeing? Or can you share a screenshot of the configuration?

Sorry @dirkjanfaber I just assumed it was a general issue. I've added ssome pictures and all the nodes are updated and the forecast solar is the latest one. When it runs it gives a warn with a red box and a long number which changes everytime you re run it. I have removed the graph output form the Subflow but it was still erroring with that. I've also tried different output options and same result. Hope its something obvious.

Config
Warn node
Warn

@Fishbowl1974
Copy link

@dirkjanfaber Dirk I've looked at the enviroment variables in the subflow and the url generation and I can't see anything wrong. to me it looks like it should be generating: https://api.forecast.solar/estimate/watthours/day/52.967/-1.7811/20/15/13.5 which if you try directly in a browser correctly works with the server.

Here is the URL bit:
url

Really can't see why it woudl suddenly stop working. i think I updated all the nodes and it stopped after that.

@dirkjanfaber
Copy link
Author

@Fishbowl1974 : I looked shortly into it last evening. What I found is that it indeed fails on the Watt hours (energy) summarized for each day selection. I found that it goes wrong in the Create graph output node. I need to look a bit deeper into actually fixing it, but if you change line 15 into: if (msg.watt === 'watt_hours_day' || msg.watt === 'watthours/day') {, the first output should at least give correct output. The graph part needs some more changes. I'll look into that during the easter weekend. 🐰

@Fishbowl1974
Copy link

@dirkjanfaber Many thanks, i'll make the adjustment.

@dirkjanfaber
Copy link
Author

dirkjanfaber commented Mar 30, 2024

@Fishbowl1974 : I've just updated the flow with a proper fix. Also generating the graphs should now work again for the Watt hours (energy) summarized for each day selection.

@Fishbowl1974
Copy link

Thanks for the update, all the best. Cheers

@QBANIN
Copy link

QBANIN commented Apr 8, 2024

Hi! Is there any way to make all properties dynamic, msg. based?

EDIT: Never mind, I changed it myself :P

@johngb1234
Copy link

Hello, thanks very much for writing this. Please forgive the newbie questions but I can't seem to get the flow to work, i am encountering the same error as @Fishbowl1974 above.
image
I would like to keep things very simple to start with and use the Forecast.Solar prediction to compare to the Victron one, I don't need the graph function for now.
thank you

@dirkjanfaber
Copy link
Author

@johngb1234 I only notice one output on that "South roof panels" node. That leads me to believe that your subflow is either very old or otherwise broken.
I suggest deleting the subflow and re-importing it (unfortunately there is no way to simply update a subflow from node-red; at least not one that I know of). In order to do so, you need to double-click on the subflow in the right menu and then on the "delete subflow" on the top and confirm.
image

After that, you can re-import the subflow again from https://gist.githubusercontent.com/dirkjanfaber/d83d3224f241ec4abf6f9f119bbee9cc/raw/70f7a00645bd74950c37f09a0c10611e5d79b822/flow.json by copy / pasting all the text in the import box (ctrl-i from node-red)

@johngb1234
Copy link

OK thanks very much Dirk. I have deleted and re-imported and I think it's clean but still receiving the same error:
image

@dirkjanfaber
Copy link
Author

@johngb1234 Can you show a screenshot of the subflows edit panel?

@johngb1234
Copy link

Thanks for the quick response.
image
image

@dirkjanfaber
Copy link
Author

I did mean a screenshot of the actual edit panel (when you double click on the subflow). Like this:
image

@johngb1234
Copy link

Ah, sorry:
image

@dirkjanfaber
Copy link
Author

This is a hard one to trace remotely. I am getting data back with exactly the same values filled out as you.

image

I am sure the warning comes from Set error status node that got triggered by the catch:all in the subflow.

image

But am a in the dark on what causes that. What version of Node-RED are you running? I tested on 3.0.2.

@johngb1234
Copy link

Thanks again for looking into this. 3.1.5 I think?
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment