Solarertrag ohne Wärmemengenmesser ausrechnen (Solarthermie)
Although the Viessmann API can provide Solar Yield values, in some cases (such as mine) these values are empty. This is because the installation has to be equipped with a calorimeter that compares the temperatures going into and going out of the buffer alongside a flow measurement (e.g. litres/minute). At least that’s what my plumber told me.
But how do you know how much energy is produced by your solar panels when there is no calorimeter?
To warm up 1 litre of water by one degree Celsius [1°C] you will need 1,1628 Watt hours [1,1628Wh]. To warm up 1000 litres of water by 10 centigrade you’ll need 11628Wh or 11,628kWh.
Our hot water tank (buffer) is normally hotter at the top and cooler at the bottom. For our workaround we just use the average temperature.
Example: Let’s assume, your buffer has 80°C at the top and 25°C at the bottom. The buffer’s average temperature is then (80+25) / 2 = 52.5°C.
That’s all we need to calculate the yield. We measure the average temperature difference between two measurements (after – before) which is then multiplied by the factor 1,1628Wh and again multiplied by the amount of water which is heated (= buffer volume).
A nice online calculator (in German) can be found here: https://gettopics.com/de/calc/wasser-erhitzen-energie-rechner
Yes, I’m simplifying in some points. I’m not taking into account any thermal losses like pipes, insulation, buffer etc. We’re calculating the net energy stored not energy produced inside the panels.
Also, the temperature distribution of a stratified charge buffer will be somewhat different and will need more temperature sensors to calculate averages that are in turn averaged.
If additional sources of energy (e.g., pellet stove or boiler) provide energy at the same time as the solar panels it’s going to be hard to attribute the energy yield to one or the other device.
What isn’t taken into account as well, is the amount of energy stored while simultaneously drawing energy out of the buffer – i.e. turning on the hot water tap.
My installation consists, amongst other stuff, of a Viessmann (Italia) Solarcell HSK 1000 combination buffer (dhw and heating) with 842 litres net volume.
It has 4 temperature sensors to control dhw preparation and the switch-over from gas to solid fuel. If you’re lucky, you can find them at
heating.dhw.sensors.temperature.hotWaterStorage.top
heating.dhw.sensors.temperature.hotWaterStorage.middle
heating.dhw.sensors.temperature.hotWaterStorage.midBottom
heating.dhw.sensors.temperature.hotWaterStorage.bottom
Unfortunately, in my case their readings aren’t included in the API dataset. I resolved that problem by plugging additional thermal sensors into the sensor openings and attaching them to my Raspberry Pi.
Let’s assume the first measured average is 41.5°C and the second 41.7°C. The difference is 0.2°C. The result is as follows
195.8 Wh or 1.958kWh is the amount of energy charged into the buffer during the measuring time. This raw measurement isn’t really telling very much because it’s just observing the (short) time span between the two measurements. To make the numbers easier to grasp, we can extrapolate the time span to one hour by applying the rule of three. Let’s assume, we take a measurement every 240 seconds.
All others being equal, our appliance would produce 2,937kWh within an hour.
For programming the following constraints apply.
To arrive at a daily value (how much energy has the sun produced today), we simply have to add up the individual raw measurements. This takes into account that the sun isn’t always sending the same amount of energy. I guess a resolution of 240 seconds is just about right. Fine enough to reflect even small changes and is long enough to reflect the water’s inertia.
Any comments are greatly appreciated.
Below please find the JSON Script for Node-Red. It reflects my situation using one wire sensors with a raspberry pi but you can easily adapt it to your setup by using the appropriate upper/lower temperature inputs. More detailed instructions in German are also available at my website https://rustimation.eu
[
{
"id": "59bbd5d9d6610b64",
"type": "tab",
"label": "Flow 1",
"disabled": false,
"info": "",
"env": []
},
{
"id": "9e523f3523b4509f",
"type": "change",
"z": "59bbd5d9d6610b64",
"name": "",
"rules": [
{
"t": "set",
"p": "lower",
"pt": "flow",
"to": "payload",
"tot": "msg"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 440,
"y": 240,
"wires": [
[
"e3bbfeceb3484e13"
]
]
},
{
"id": "97b17f3fe012ed6d",
"type": "change",
"z": "59bbd5d9d6610b64",
"name": "",
"rules": [
{
"t": "set",
"p": "upper",
"pt": "flow",
"to": "payload",
"tot": "msg"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 440,
"y": 180,
"wires": [
[
"e3bbfeceb3484e13"
]
]
},
{
"id": "e3bbfeceb3484e13",
"type": "join",
"z": "59bbd5d9d6610b64",
"name": "",
"mode": "custom",
"build": "array",
"property": "payload",
"propertyType": "msg",
"key": "topic",
"joiner": "\\n",
"joinerType": "str",
"accumulate": false,
"timeout": "",
"count": "2",
"reduceRight": false,
"reduceExp": "",
"reduceInit": "",
"reduceInitType": "",
"reduceFixup": "",
"x": 590,
"y": 220,
"wires": [
[
"15522f64cf0b0750"
]
]
},
{
"id": "15522f64cf0b0750",
"type": "function",
"z": "59bbd5d9d6610b64",
"name": "avgTemperature",
"func": "msg.payload = (flow.get('upper') + flow.get('lower')) / 2;\nmsg.topic = \"Durchschnitt\"\n\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 760,
"y": 220,
"wires": [
[
"e392afc8b5558bcd",
"1ed8cd3832270c2f"
]
]
},
{
"id": "e392afc8b5558bcd",
"type": "debug",
"z": "59bbd5d9d6610b64",
"name": "checker",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 780,
"y": 160,
"wires": []
},
{
"id": "0750b57e51f02cf4",
"type": "function",
"z": "59bbd5d9d6610b64",
"name": "calculate yield",
"func": "var sPump = global.get(\"solPump\");\nvar timeBetween = (flow.get(\"1st\") - flow.get(\"2nd\")) / 1000;\nvar tempDiff = flow.get(\"T1\") - flow.get(\"T2\");\nif (isNaN(flow.get('solarYield'))) {\n flow.set('solarYield', 0);\n}\nvar kyield = flow.get(\"solarYield\");\nmsg.payload = 0;\n\nif (tempDiff > 0){ \n if (sPump == \"on\") {\n kyield = kyield + tempDiff * 0.979; //accumulate: \n flow.set('solarYield',kyield);\n // Rule of three - extraolate to one operating hour\n msg.payload = tempDiff / timeBetween * 3600 * 0.979;\n };\n \n};\nmsg.yield = kyield;\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1160,
"y": 280,
"wires": [
[
"0a6d6c280ff6b97d",
"1ce1438171127c64",
"a382d7914c04f8bf"
]
]
},
{
"id": "5a4f4b3c9f8a5110",
"type": "change",
"z": "59bbd5d9d6610b64",
"name": "",
"rules": [
{
"t": "set",
"p": "T2",
"pt": "flow",
"to": "T1",
"tot": "flow"
},
{
"t": "set",
"p": "T1",
"pt": "flow",
"to": "payload",
"tot": "msg"
},
{
"t": "set",
"p": "2nd",
"pt": "flow",
"to": "1st",
"tot": "flow"
},
{
"t": "set",
"p": "1st",
"pt": "flow",
"to": "myepoch",
"tot": "msg"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 950,
"y": 280,
"wires": [
[
"0750b57e51f02cf4"
]
]
},
{
"id": "1ed8cd3832270c2f",
"type": "simpletime",
"z": "59bbd5d9d6610b64",
"name": "",
"mydate": false,
"myymd": false,
"myyear": false,
"mymonth": false,
"mymonthn": false,
"mydom": false,
"mydoy": false,
"myday": false,
"myhourpm": false,
"myhour": false,
"mytime": false,
"mytimes": false,
"myminute": false,
"myminutes": false,
"mysecond": false,
"mymillis": false,
"myepoch": true,
"myrawdate": false,
"mypm": false,
"x": 770,
"y": 280,
"wires": [
[
"5a4f4b3c9f8a5110"
]
]
},
{
"id": "1ce1438171127c64",
"type": "ui_gauge",
"z": "59bbd5d9d6610b64",
"name": "",
"group": "c29d2dfd96cd5c3d",
"order": 5,
"width": 3,
"height": 2,
"gtype": "gage",
"title": "current yield",
"label": "kWh",
"format": "{{value|number:1}}",
"min": 0,
"max": 10,
"colors": [
"#c0c0c0",
"#ff8000",
"#ca3838"
],
"seg1": "",
"seg2": "",
"className": "",
"x": 1430,
"y": 240,
"wires": []
},
{
"id": "0a6d6c280ff6b97d",
"type": "debug",
"z": "59bbd5d9d6610b64",
"name": "yield",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 1410,
"y": 180,
"wires": []
},
{
"id": "a382d7914c04f8bf",
"type": "ui_text",
"z": "59bbd5d9d6610b64",
"group": "c29d2dfd96cd5c3d",
"order": 6,
"width": 5,
"height": 1,
"name": "",
"label": "yield today",
"format": "{{msg.yield|number:1}} kWh",
"layout": "row-left",
"className": "",
"x": 1430,
"y": 280,
"wires": []
},
{
"id": "ec95fb41a0eb6792",
"type": "ui_text",
"z": "59bbd5d9d6610b64",
"group": "c29d2dfd96cd5c3d",
"order": 8,
"width": 5,
"height": 1,
"name": "",
"label": "Solar Yield yesterday:",
"format": "{{msg.payload|number:1}} kWh",
"layout": "row-left",
"className": "",
"x": 700,
"y": 380,
"wires": []
},
{
"id": "1bc6c955bb779f1d",
"type": "function",
"z": "59bbd5d9d6610b64",
"name": "",
"func": "msg.payload = msg.payload.toFixed(1);\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"x": 500,
"y": 380,
"wires": [
[
"ec95fb41a0eb6792"
]
]
},
{
"id": "2d463ddb6504eebd",
"type": "change",
"z": "59bbd5d9d6610b64",
"name": "to zero",
"rules": [
{
"t": "set",
"p": "payload",
"pt": "msg",
"to": "solarYield",
"tot": "flow"
},
{
"t": "set",
"p": "solarYield",
"pt": "flow",
"to": "0",
"tot": "num"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 350,
"y": 380,
"wires": [
[
"1bc6c955bb779f1d"
]
]
},
{
"id": "a2d75f230559f016",
"type": "inject",
"z": "59bbd5d9d6610b64",
"name": "",
"props": [
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "00 00 * * *",
"once": false,
"onceDelay": 0.1,
"topic": "NullUhr",
"payloadType": "str",
"x": 200,
"y": 380,
"wires": [
[
"2d463ddb6504eebd"
]
]
},
{
"id": "7581a70d7931b2a6",
"type": "function",
"z": "59bbd5d9d6610b64",
"name": "Solar Pump Status",
"func": "var featureArray=msg.payload.data;\nvar idx = featureArray.findIndex( (element) => element.feature === 'heating.solar.pumps.circuit');\nmsg.payload=msg.payload.data[idx].properties.status.value;\nglobal.set(\"solPump\", msg.payload); //setze globale Variable für Solarpumpe\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 450,
"y": 100,
"wires": [
[
"c07c86025680ed64"
]
]
},
{
"id": "559c3b397932b52a",
"type": "sensor-ds18b20",
"z": "59bbd5d9d6610b64",
"name": "Oben",
"topic": "",
"sensorid": "28-01203263201c",
"timer": "240",
"repeat": true,
"x": 230,
"y": 180,
"wires": [
[
"97b17f3fe012ed6d"
]
]
},
{
"id": "d5c8a3d01c2f8726",
"type": "sensor-ds18b20",
"z": "59bbd5d9d6610b64",
"name": "Unten",
"topic": "",
"sensorid": "28-0120324ccdd9",
"timer": "240",
"repeat": true,
"x": 230,
"y": 240,
"wires": [
[
"9e523f3523b4509f"
]
]
},
{
"id": "c07c86025680ed64",
"type": "change",
"z": "59bbd5d9d6610b64",
"name": "",
"rules": [
{
"t": "set",
"p": "solPump",
"pt": "msg",
"to": "payload",
"tot": "msg"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 680,
"y": 100,
"wires": [
[]
]
},
{
"id": "dbd0398e1f70e0d5",
"type": "comment",
"z": "59bbd5d9d6610b64",
"name": "Get Solar Pump status from API",
"info": "Status of Solar pump derived from API Feature Request.\nsee: https://www.viessmann-community.com/t5/The-Viessmann-API/Using-Node-Red-to-visualize-ViCare-data/td-p/195606",
"x": 500,
"y": 40,
"wires": []
},
{
"id": "915af2c4de7754da",
"type": "comment",
"z": "59bbd5d9d6610b64",
"name": "reset daily value & carry over",
"info": "",
"x": 250,
"y": 340,
"wires": []
},
{
"id": "af1934c83a909136",
"type": "comment",
"z": "59bbd5d9d6610b64",
"name": "adapt input before deploy",
"info": "",
"x": 230,
"y": 140,
"wires": []
},
{
"id": "c29d2dfd96cd5c3d",
"type": "ui_group",
"name": "Solar",
"tab": "7e5ee24d.2d5ae4",
"order": 1,
"disp": true,
"width": "6",
"collapse": false,
"className": ""
},
{
"id": "7e5ee24d.2d5ae4",
"type": "ui_tab",
"name": "Solar/Heizung",
"icon": "dashboard",
"order": 4,
"disabled": false,
"hidden": false
}
]