Sure. No problems.
The node red flow that follows does both a download of a forecast which is then uploaded to EmonCMS and well as the posting of my actuals. The code for the posting of the actuals is complicated by making sure I only post when the output of the solar array is not constrained by the battery state. This is identified when the line frequency is above 51Hz and so I discard any measurement period where the frequency rises above 51Hz. You can edit is part out to get it working all the time.
[
{
"id": "e77f9f87.0c622",
"type": "tab",
"label": "Solcast Interface",
"disabled": false,
"info": ""
},
{
"id": "66dca2c9.3eb33c",
"type": "http request",
"z": "e77f9f87.0c622",
"name": "Solcast Forecast Data",
"method": "GET",
"ret": "txt",
"paytoqs": false,
"url": "https://api.solcast.com.au/rooftop_sites/4c59-af78-a1a0-ee25/forecasts?format=json",
"tls": "",
"persist": false,
"proxy": "",
"authType": "basic",
"x": 380,
"y": 190,
"wires": [
[
"4c9657e6.2bab08"
]
]
},
{
"id": "fd8e1b49.3c49b8",
"type": "debug",
"z": "e77f9f87.0c622",
"name": "View Full Output",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"x": 640,
"y": 230,
"wires": []
},
{
"id": "4c9657e6.2bab08",
"type": "json",
"z": "e77f9f87.0c622",
"name": "Convert from JSON to Object",
"property": "payload",
"action": "",
"pretty": false,
"x": 620,
"y": 190,
"wires": [
[
"fd8e1b49.3c49b8",
"91d934a.f8843c8"
]
]
},
{
"id": "91d934a.f8843c8",
"type": "function",
"z": "e77f9f87.0c622",
"name": "Reset Daily Forecast Data",
"func": "var daily_forecast_data = [];\nfor (i = 0; i <= 120; i++) { // 0 is the 0530 forecast slot. First forecast at 0500 is 0530.\n forecast_output = msg.payload.forecasts[i].pv_estimate\n daily_forecast_data.push(forecast_output);\n}\nflow.set(\"daily_forecast_data\",daily_forecast_data);\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"x": 970,
"y": 190,
"wires": [
[
"a3b4c0b3.ec825"
]
]
},
{
"id": "1c137609.ce1dba",
"type": "inject",
"z": "e77f9f87.0c622",
"name": "At 0500 each day",
"props": [
{
"p": "payload",
"v": "",
"vt": "date"
},
{
"p": "topic",
"v": "",
"vt": "string"
}
],
"repeat": "",
"crontab": "00 05 * * *",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 170,
"y": 190,
"wires": [
[
"66dca2c9.3eb33c"
]
]
},
{
"id": "ece67ca9.59b8f",
"type": "mqtt out",
"z": "e77f9f87.0c622",
"name": "",
"topic": "emon/Solcast/DailyForecast",
"qos": "2",
"retain": "true",
"broker": "36372260.fb268e",
"x": 1520,
"y": 240,
"wires": []
},
{
"id": "42495fd6.c9b48",
"type": "inject",
"z": "e77f9f87.0c622",
"name": "Test Poke",
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 460,
"y": 310,
"wires": [
[
"a5bfbde5.eff4b"
]
]
},
{
"id": "a5bfbde5.eff4b",
"type": "function",
"z": "e77f9f87.0c622",
"name": "Get forecast_object",
"func": "msg.payload = flow.get(\"forecast_object\")\nreturn msg;",
"outputs": 1,
"noerr": 0,
"x": 650,
"y": 310,
"wires": [
[
"a3b4c0b3.ec825"
]
]
},
{
"id": "568c3c8b.1cbb94",
"type": "debug",
"z": "e77f9f87.0c622",
"name": "",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"x": 1493,
"y": 434,
"wires": []
},
{
"id": "a3b4c0b3.ec825",
"type": "function",
"z": "e77f9f87.0c622",
"name": "Format Forecast to Load into Feed, Update Daily Total Estimate",
"func": "latest_forecast_time = flow.get(\"latest_forecast_time\") || 0;\nthis_forecast_call_time = global.get(\"hour_of_day\");\ndaily_forecast_data = flow.get(\"daily_forecast_data\");\nrecord_id_2130_tomorrow = 80 - ((this_forecast_call_time - 5) * 2);\nvar msg2 = { payload: 0};\nvar msg3 = { method: \"POST\" };\nflow.set(\"forecast_object\",msg.payload); \nfor (i = 0; i < 96; i++) { // Load forecast data into emoncms for next 48 hrs. First forecast is for next 30min.\n forecast_output = msg.payload.forecasts[i].pv_estimate * 1000;\n forecast_time = (new Date(msg.payload.forecasts[i].period_end).getTime() / 1000 );\n if (forecast_time <= latest_forecast_time) {\n msg3.url = \"http://192.168.1.200/emoncms/feed/update.json?id=92&time=\" + forecast_time + \"&value=\" + forecast_output + \"&apikey=5af722e3ef5403b7d1deb3e8ff4450e1\";\n\n } else {\n msg3.url = \"http://192.168.1.200/emoncms/feed/insert.json?id=92&time=\" + forecast_time + \"&value=\" + forecast_output + \"&apikey=5af722e3ef5403b7d1deb3e8ff4450e1\";\n flow.set(\"latest_forecast_time\", forecast_time);\n }\n node.send([null, null, msg3]);\n if (i <= record_id_2130_tomorrow) {\n daily_forecast_data[i + ((this_forecast_call_time - 5) * 2)] = forecast_output/1000; // 0 is the 0530 slot. First forecast at 0500 is 0530.\n }\n}\n// Sum forecast values for daily forecast for current day\nforecast_total = 0;\nfor (i = 0; i <=32; i++) { // 0 is the 0530 slot. 32 is the 2130 slot. First forecast at 0500 is 0530.\n forecast_total += daily_forecast_data[i];\n}\nforecast_total = forecast_total / 2;\nmsg.payload = forecast_total;\nflow.set(\"24hr_forecast\", forecast_total);\n// Sum forecast values for daily forecast for next day\nforecast_total = 0;\nfor (i = 48; i <=80; i++) { // 48 is the 0530 slot. 80 is the 2130 slot. First forecast at 0500 is 0530.\n forecast_total += daily_forecast_data[i];\n}\nforecast_total = forecast_total / 2;\nmsg2.payload = forecast_total;\nflow.set(\"48hr_forecast\", forecast_total);\nflow.set(\"daily_forecast_data\", daily_forecast_data);\nreturn [msg, msg2, null];",
"outputs": 3,
"noerr": 0,
"initialize": "",
"finalize": "",
"x": 1090,
"y": 350,
"wires": [
[
"227a3047.4dbae",
"ece67ca9.59b8f"
],
[
"6dc37916.c105d8"
],
[
"30124093.5d5a9"
]
]
},
{
"id": "30124093.5d5a9",
"type": "delay",
"z": "e77f9f87.0c622",
"name": "Rate Limiter",
"pauseType": "rate",
"timeout": "5",
"timeoutUnits": "seconds",
"rate": "1",
"nbRateUnits": "1",
"rateUnits": "second",
"randomFirst": "1",
"randomLast": "5",
"randomUnits": "seconds",
"drop": false,
"x": 1473,
"y": 374,
"wires": [
[
"568c3c8b.1cbb94",
"91f7e39f.fc9d6"
]
]
},
{
"id": "91f7e39f.fc9d6",
"type": "http request",
"z": "e77f9f87.0c622",
"name": "http post to emoncms of forecast point",
"method": "use",
"ret": "txt",
"paytoqs": false,
"url": "",
"tls": "",
"persist": false,
"proxy": "",
"authType": "",
"x": 1753,
"y": 374,
"wires": [
[
"423efc4c.78f844"
]
]
},
{
"id": "423efc4c.78f844",
"type": "debug",
"z": "e77f9f87.0c622",
"name": "Response",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"x": 1743,
"y": 434,
"wires": []
},
{
"id": "603435d.861a6cc",
"type": "mqtt in",
"z": "e77f9f87.0c622",
"name": "",
"topic": "emon/emontx3cm15/P1",
"qos": "2",
"datatype": "auto",
"broker": "36372260.fb268e",
"x": 180,
"y": 730,
"wires": [
[
"47b07f5.016868"
]
]
},
{
"id": "35809296.8b0cce",
"type": "inject",
"z": "e77f9f87.0c622",
"name": "Every 5 minutes",
"props": [
{
"p": "payload",
"v": "5",
"vt": "num"
},
{
"p": "topic",
"v": "",
"vt": "string"
}
],
"repeat": "",
"crontab": "*/5 5-20 * * *",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "5",
"payloadType": "num",
"x": 170,
"y": 610,
"wires": [
[
"7254cdc7.0401b4"
]
]
},
{
"id": "7254cdc7.0401b4",
"type": "function",
"z": "e77f9f87.0c622",
"name": "Calculate Average Solar Power",
"func": "// If PV panels have not been limited in the past period\n// calculate average solar power\nvar solar_history = [];\nvar frequency_history = [];\nvar average_solar_power = 0;\nvar solar_total = 0;\nvar period = msg.payload;\nsolar_history = flow.get(\"solar_history\") || [];\nfrequency_history = global.get(\"frequency_history\");\n\n// Calculate average solar power\nsolar_intervals = solar_history.length;\nfor (i = 0; i < solar_intervals; i++) {\n solar_total += solar_history[i];\n}\naverage_solar_power = solar_total / solar_intervals;\n\n// Clear solar_history array\nsolar_history = [];\nflow.set(\"solar_history\", solar_history);\n\n// Cleck to see if PV panels have been limited\nfrequency_intervals = period * 6;\nstatus = 0;\nfor (i = 0; i < frequency_intervals; i++) {\n if (frequency_history[i] > 51) {\n status++;\n }\n}\n\n// If PV panels have not been limited send on data\nif (status === 0) {\n msg.solar_total = solar_total;\n msg.solar_intervals = solar_intervals;\n msg.average_solar_power = average_solar_power;\n msg.period = period;\n} else {\n msg = null; \n}\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"x": 470,
"y": 610,
"wires": [
[
"1c3a7ed0.68d9a1",
"7e6b6634.6748d8"
]
]
},
{
"id": "1c3a7ed0.68d9a1",
"type": "debug",
"z": "e77f9f87.0c622",
"name": "",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"x": 550,
"y": 650,
"wires": []
},
{
"id": "47b07f5.016868",
"type": "function",
"z": "e77f9f87.0c622",
"name": "Push into solar_history array",
"func": "var solar_history = [];\nsolar_history = flow.get(\"solar_history\") || [];\ncurrent_solar_power = parseInt(msg.payload);\nif (current_solar_power < 0) {\n current_solar_power = 0;\n}\nsolar_history.push(current_solar_power);\nflow.set(\"solar_history\", solar_history);\nmsg.payload = solar_history;\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"x": 460,
"y": 730,
"wires": [
[]
]
},
{
"id": "7e6b6634.6748d8",
"type": "change",
"z": "e77f9f87.0c622",
"name": "Set timestamp in ISO 8601 format",
"rules": [
{
"t": "set",
"p": "timestamp",
"pt": "msg",
"to": "$now()",
"tot": "jsonata"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 820,
"y": 610,
"wires": [
[
"2e4ff927.8cb916"
]
]
},
{
"id": "2e4ff927.8cb916",
"type": "function",
"z": "e77f9f87.0c622",
"name": "Format for publishing to Solcast",
"func": "var measurement = { measurement: { period: \"PT\" + msg.period + \"M\", period_end: msg.timestamp, total_power: msg.average_solar_power / 1000 }};\nmsg.payload = measurement;\nreturn msg;",
"outputs": 1,
"noerr": 0,
"x": 1150,
"y": 610,
"wires": [
[
"65c3c1c7.d02e6",
"dc8ade32.dd41c"
]
]
},
{
"id": "dc8ade32.dd41c",
"type": "http request",
"z": "e77f9f87.0c622",
"name": "http post to Solcast of average solar power",
"method": "POST",
"ret": "txt",
"paytoqs": true,
"url": "https://api.solcast.com.au/rooftop_sites/4c59-af78-a1a0-ee25/measurements",
"tls": "",
"persist": false,
"proxy": "",
"authType": "basic",
"x": 1500,
"y": 610,
"wires": [
[
"d97da441.8ece88"
]
]
},
{
"id": "65c3c1c7.d02e6",
"type": "debug",
"z": "e77f9f87.0c622",
"name": "",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"x": 1170,
"y": 650,
"wires": []
},
{
"id": "d97da441.8ece88",
"type": "debug",
"z": "e77f9f87.0c622",
"name": "Response",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"x": 1540,
"y": 650,
"wires": []
},
{
"id": "697c0642.c844a8",
"type": "inject",
"z": "e77f9f87.0c622",
"name": "Every hour",
"repeat": "",
"crontab": "0 6-20 * * *",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 150,
"y": 350,
"wires": [
[
"cf26c8cc.a7ddb8"
]
]
},
{
"id": "cf26c8cc.a7ddb8",
"type": "http request",
"z": "e77f9f87.0c622",
"name": "Solcast Forecast Data",
"method": "GET",
"ret": "txt",
"paytoqs": false,
"url": "https://api.solcast.com.au/rooftop_sites/4c59-af78-a1a0-ee25/forecasts?format=json",
"tls": "",
"persist": false,
"proxy": "",
"authType": "basic",
"x": 350,
"y": 350,
"wires": [
[
"60ddbfef.944"
]
]
},
{
"id": "60ddbfef.944",
"type": "json",
"z": "e77f9f87.0c622",
"name": "Convert from JSON to Object",
"property": "payload",
"action": "",
"pretty": false,
"x": 620,
"y": 350,
"wires": [
[
"a3b4c0b3.ec825"
]
]
},
{
"id": "4588b3cc.d924dc",
"type": "comment",
"z": "e77f9f87.0c622",
"name": "Update forecast data each hour",
"info": "",
"x": 170,
"y": 290,
"wires": []
},
{
"id": "c5174864.24a4a8",
"type": "comment",
"z": "e77f9f87.0c622",
"name": "Reset total daily solar forecast",
"info": "",
"x": 160,
"y": 150,
"wires": []
},
{
"id": "5dfe3d80.2eecf4",
"type": "comment",
"z": "e77f9f87.0c622",
"name": "Send acutals to Solcast",
"info": "",
"x": 140,
"y": 560,
"wires": []
},
{
"id": "227a3047.4dbae",
"type": "debug",
"z": "e77f9f87.0c622",
"name": "",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"x": 1450,
"y": 200,
"wires": []
},
{
"id": "dc8b581d.d24778",
"type": "inject",
"z": "e77f9f87.0c622",
"name": "",
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "39.157",
"payloadType": "num",
"x": 1290,
"y": 140,
"wires": [
[
"ece67ca9.59b8f"
]
]
},
{
"id": "fa4446a0.3b08d8",
"type": "comment",
"z": "e77f9f87.0c622",
"name": "Initialise",
"info": "",
"x": 100,
"y": 40,
"wires": []
},
{
"id": "ff28e9b.8532c18",
"type": "inject",
"z": "e77f9f87.0c622",
"name": "Initialise",
"repeat": "",
"crontab": "",
"once": true,
"onceDelay": "0.1",
"topic": "",
"payload": "",
"payloadType": "date",
"x": 140,
"y": 80,
"wires": [
[
"d04d3d3c.d80fe"
]
]
},
{
"id": "d04d3d3c.d80fe",
"type": "function",
"z": "e77f9f87.0c622",
"name": "Initialise Flow Variables",
"func": "var daily_forecast_data = [];\nfor (i = 0; i <= 32; i++) { // 0 is the 0530 forecast slot. First forecast at 0500 is 0530.\n daily_forecast_data.push(0);\n}\nflow.set(\"daily_forecast_data\",daily_forecast_data);\nmsg.daily_forecast_data = daily_forecast_data;\nreturn msg;",
"outputs": 1,
"noerr": 0,
"x": 390,
"y": 80,
"wires": [
[
"17ea05ff.2a1a5a"
]
]
},
{
"id": "17ea05ff.2a1a5a",
"type": "debug",
"z": "e77f9f87.0c622",
"name": "Initialised Flow Variables",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"x": 670,
"y": 80,
"wires": []
},
{
"id": "6dc37916.c105d8",
"type": "mqtt out",
"z": "e77f9f87.0c622",
"name": "",
"topic": "emon/Solcast/NextDailyForecast",
"qos": "2",
"retain": "true",
"broker": "36372260.fb268e",
"x": 1530,
"y": 300,
"wires": []
},
{
"id": "1e2f015b.c3649f",
"type": "inject",
"z": "e77f9f87.0c622",
"name": "Initialise",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": true,
"onceDelay": "45",
"topic": "",
"payload": "",
"payloadType": "date",
"x": 140,
"y": 225,
"wires": [
[
"66dca2c9.3eb33c"
]
]
},
{
"id": "36372260.fb268e",
"type": "mqtt-broker",
"z": "",
"name": "emonbase mqtt broker",
"broker": "localhost",
"port": "1883",
"clientid": "",
"usetls": false,
"compatmode": false,
"keepalive": "60",
"cleansession": true,
"birthTopic": "",
"birthQos": "0",
"birthPayload": "",
"closeTopic": "",
"closeQos": "0",
"closePayload": "",
"willTopic": "",
"willQos": "0",
"willPayload": ""
}
]
Please let me know how you go.