Heat Network Designer

From Heatweb Wiki
Revision as of 23:32, 31 March 2022 by Rhg (talk | contribs) (Created page with "==Link to Online Heat Network Designer== https://hw7.ddns.net/ui/hndesign ==Node-RED Flow== <pre> [{"id":"875294b.9c94968","type":"http in","z":"cca63413.fec0b8","name":""...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigationJump to search

Link to Online Heat Network Designer

https://hw7.ddns.net/ui/hndesign


Node-RED Flow

[{"id":"875294b.9c94968","type":"http in","z":"cca63413.fec0b8","name":"","url":"/ui/hndesign","method":"get","upload":false,"swaggerDoc":"","x":220,"y":4680,"wires":[["3dc7abcb.50da24"]]},{"id":"3dc7abcb.50da24","type":"template","z":"cca63413.fec0b8","name":"Heat Network Designer","field":"payload.body","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"\n<script src=\"https://bossanova.uk/jspreadsheet/v4/jexcel.js\"></script>\n<link rel=\"stylesheet\" href=\"https://bossanova.uk/jspreadsheet/v4/jexcel.css\" type=\"text/css\" />\n \n<script src=\"https://jsuites.net/v4/jsuites.js\"></script>\n<link rel=\"stylesheet\" href=\"https://jsuites.net/v4/jsuites.css\" type=\"text/css\" />\n\n<script src=\"https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.1/jquery.validate.min.js\"></script>\n{{!--https://www.sitepoint.com/basic-jquery-form-validation-tutorial/--}}\n\n<script src=\"https://unpkg.com/mustache@latest\"></script>\n\n<style>\n\n.section {\n    margin: auto;\n    min-height: 20px;\n    max-width: 900px;\n    padding: 19px;\n    margin-bottom: 20px;\n    background-color: #f5f5f5;\n    border: 1px solid #e3e3e3;\n    border-radius: 4px;\n    box-shadow: inset 0 1px 1px rgb(0 0 0 / 5%);\n}\n\n.question {\n    min-height: 20px;\n    padding: 19px;\n    margin-bottom: 20px;\n    background-color: #a8e5a8;\n    border: 1px solid #e3e3e3;\n    border-radius: 4px;\n    box-shadow: inset 0 1px 1px rgb(0 0 0 / 5%);\n}\n\n\n.qtext {\n    \n    padding: 6px 0px 12px;\n    font-size: 18px;\n    font-weight: bold;\n}\n.notes {\n    \n    padding: 0px 0px 12px;\n    font-size: 16px;\n}\n.qunits {\n    \n    padding: 10px 10px 0px;\n}\n\n.video-wrapper {\n\tposition: relative;\n\tpadding-bottom: 56.25%; /* 16:9 */\n\tpadding-top: 15px;\n\theight: 0;\n\tmargin: 10px 0;\n}\n.video-wrapper iframe {\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n\twidth: 100%;\n\theight: 100%;\n}\n\nform .error {\n  color: #ff0000;\n  font-size: 16px;\n}\n\n::-webkit-input-placeholder { /* Edge */\n  color: yellow;\n}\n\n:-ms-input-placeholder { /* Internet Explorer 10-11 */\n  color: yellow;\n}\n\n::placeholder {\n  color: yellow;\n}\n\n</style>\n<script type=\"text/javascript\">\n\nvar a = new Date();\nvar qdata;\nvar calculated = {};\n\t\n\t$(document).ready(function(e) {\n    \n        // $(\"form[ajax=true]\").submit(function(e) {\n            \n        //     //alert(\"submit\");\n            \n        //     e.preventDefault();\n            \n            \n            var form_data = {};\n            var form_url = \"/ui/qdata\";\n            var form_target = $(this).attr(\"target\") || \"result\";\n            var ftarget = \"#\" + form_target;\n            var form_method = \"GET\";\n            \n        //     $(\"#loadingimg\").show();\n            \n            $.ajax({\n                url: form_url, \n                type: form_method,      \n                data: form_data,     \n                cache: false,\n                success: function(results){   \n                    \n                    qdata = results;\n                    //qdata = JSON.parse(returnhtml);\n                    console.log(qdata);\n                   \n                    document.getElementById(\"section1\").innerHTML = parseQuestions(qdata); //JSON.stringify(qdata) ;\n                    \n                    validform();\n                                  \n                }           \n            });    \n            \n        // });\n        \n      \n        \n    });\n\t\nfunction parseQuestions(qdata) {\n    \n    console.log(qdata);\n    console.log(calculated);\n    \n    var oot = \"\";\n    for (var s in qdata[\"sections\"]) {\n        console.log(s);\n        console.log(qdata.sections[s]);\n        oot += \"<div class='section'><h2>\" + qdata.sections[s].title + \"</h2>\\n\";\n        \n        for (var q in qdata.sections[s].questions) {\n            \n            \n            var qif = qdata.sections[s].questions[q].if || true;\n            var qift = Mustache.render(qif, calculated);\n            \n            var goq = true;\n            try {\n              goq = looseJsonParse(qift); \n            }\n            catch(err) {\n              goq = false;\n            }\n            \n            var v=\"\";\n            if (calculated[qdata.sections[s].questions[q].id]) { v= calculated[qdata.sections[s].questions[q].id] ; }\n            //else if (qdata.sections[s].questions[q].default) { v= qdata.sections[s].questions[q].default ; }\n            \n            if(goq) {\n            \n                if (v!==\"\") { oot += '<div class=\"question\" style=\"background-color: #d2efd2;\">';  } else { oot += '<div class=\"question\">'; }\n                \n                //oot += \"<div class='qtext'>\" + qdata.sections[s].questions[q].title + '</div>';\n                oot += '<label for=\"' + qdata.sections[s].questions[q].id + '\" class=\"qtext\">' + (qdata.sections[s].questions[q].question||qdata.sections[s].questions[q].title) + '</label>';\n                \n                if (qdata.sections[s].questions[q].notes) { oot += '<div class=\"notes\">'  + convertLinks(qdata.sections[s].questions[q].notes) + '</div>'; }\n                oot += '<table width=\"100%\"><tr><td width=\"80%\"><input id=\"' + qdata.sections[s].questions[q].id + '\" name=\"' + qdata.sections[s].questions[q].id + '\" class=\"form-control\" type=\"text\"';\n                \n                if (v!==\"\") { oot += ' value=\"' + v + '\"'; }\n                // if (calculated[qdata.sections[s].questions[q].id]) { oot += ' value=\"' + calculated[qdata.sections[s].questions[q].id] + '\"'; }\n                // else if (qdata.sections[s].questions[q].default) { oot += ' value=\"' + qdata.sections[s].questions[q].default + '\"'; }\n                if (qdata.sections[s].questions[q].default) { oot += ' placeholder=\"' + qdata.sections[s].questions[q].default + \" \" + (qdata.sections[s].questions[q].units||\"\") + ' ... please confirm\"'; }\n                \n                oot += '></td><td valign=\"top\"><span class=\"qunits\">' + (qdata.sections[s].questions[q].units||\"\") + '</span></td></tr></table></div>\\n';\n            }\n        }\n    \n        oot += \"</div>\\n\";\n        \n    }\n    \n    \n    return oot;\n}\n\nfunction looseJsonParse(obj) {\n    return Function('\"use strict\";return (' + obj + ')')();\n}\n// console.log(looseJsonParse(\n//   \"{a:(4-1), b:function(){}, c:new Date()}\"\n// ))\n\nfunction runCalcs() {\n\n\n    \n    \n    // run through form inputs and store current values into calculated.\n    var cells =  document.getElementsByClassName(\"form-control\");\n\tvar i;\n\tfor (i = 0; i < cells.length; i++) {\n\t\n\t\tcalcsOut += cells[i].value + \"<br>\\n\";\n        \n        if (\"\" + cells[i].value !== \"\") { \n            \n            if (isNaN(cells[i].value)) { \n                \n                calculated[cells[i].id] = (cells[i].value);\n                \n            } else {\n                \n                calculated[cells[i].id] = parseFloat(cells[i].value);\n            }\n        }\n\t}\n    \n    var calcsOut = '<table class=\"table\">';\n    for (var calc in (qdata[\"calculations\"]||[])) {\n        \n        console.log(calc);\n        console.log(qdata.calculations[calc]);\n        var f = qdata.calculations[calc].function;\n        var ft = Mustache.render(f, calculated);\n        \n        calcsOut += \"<tr>\";  //<td>\" + qdata.calculations[calc].id + \"</td>\";\n        calcsOut += \"<td>\" + (qdata.calculations[calc].title||qdata.calculations[calc].id) + \"</td>\";\n        \n        //calcsOut += f + \" = \";\n        calcsOut += \"<td><small>\" + ft + \"</small></td>\";   //\"<br>\\n\";\n        \n        var cv = \"\";\n        try {\n          cv = looseJsonParse(ft); //  looseJsonParse(\"{a:(4-1), b:function(){}, c:new Date()}\");\n        }\n        catch(err) {\n          \n        }\n         \n        \n        if (!isNaN(cv)) { cv = parseInt(cv*1000)/1000; }\n        \n        calcsOut +=  '<td align=\"right\">' + cv + \"</td>\";\n        \n        calcsOut +=  \"<td>\" + (qdata.calculations[calc].units||\"\") + \"</td></tr>\\n\";\n        \n        calculated[qdata.calculations[calc].id] = cv;\n        \n    }\n    \n    calcsOut +=  \"</table>\\n\";\n    \n    // for (var calc in calculated) {\n        \n        \n    // }\n    \n    document.getElementById(\"calcs\").innerHTML = calcsOut; //JSON.stringify(qdata) ;\n    \n    // rebuild form\n    document.getElementById(\"section1\").innerHTML = parseQuestions(qdata); //JSON.stringify(qdata) ;\n    validform();\n                    \n}\n\n\nconst convertLinks = ( input ) => {\n\n  let text = input;\n  const linksFound = text.match( /(?:www|https?)[^\\s]+/g );\n  const aLink = [];\n\n  if ( linksFound != null ) {\n\n    for ( let i=0; i<linksFound.length; i++ ) {\n      let replace = linksFound[i];\n      if ( !( linksFound[i].match( /(http(s?)):\\/\\// ) ) ) { replace = 'http://' + linksFound[i] }\n      let linkText = replace.split( '/' )[2];\n      if ( linkText.substring( 0, 3 ) == 'www' ) { linkText = linkText.replace( 'www.', '' ) }\n      if ( linkText.match( /youtu/ ) ) {\n\n        let youtubeID = replace.split( '/' ).slice(-1)[0];\n        aLink.push( '<div class=\"video-wrapper\"><iframe src=\"https://www.youtube.com/embed/' + youtubeID + '\" frameborder=\"0\" allow=\"accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe></div>' )\n      }\n      else if ( linkText.match( /vimeo/ ) ) {\n        let vimeoID = replace.split( '/' ).slice(-1)[0];\n        aLink.push( '<div class=\"video-wrapper\"><iframe src=\"https://player.vimeo.com/video/' + vimeoID + '\" frameborder=\"0\" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe></div>' )\n      }\n      else {\n        aLink.push( '<a href=\"' + replace + '\" target=\"_blank\">' + linkText + '</a>' );\n      }\n      text = text.split( linksFound[i] ).map(item => { return aLink[i].includes('iframe') ? item.trim() : item } ).join( aLink[i] );\n    }\n    return text;\n\n  }\n  else {\n    return input;\n  }\n}\n\nfunction validform() {\n// Initialize form validation on the registration form.\n  // It has the name attribute \"registration\"\n  $(\"form[name='qform']\").validate({\n    // Specify validation rules\n    rules: {\n      // The key name on the left side is the name attribute\n      // of an input field. Validation rules are defined\n      // on the right side\n      nProperties: {\n        required: true,\n        digits: true,\n        min: 1\n      },\n      lastname: \"required\",\n      email: {\n        required: true,\n        // Specify that email should be validated\n        // by the built-in \"email\" rule\n        email: true\n      },\n      password: {\n        required: true,\n        minlength: 5\n      }\n    },\n    // Specify validation error messages\n    messages: {\n      nProperties: \"Please enter a number.\",\n      lastname: \"Please enter your lastname\",\n      password: {\n        required: \"Please provide a password\",\n        minlength: \"Your password must be at least 5 characters long\"\n      },\n      email: \"Please enter a valid email address\"\n    },\n    // Make sure the form is submitted to the destination defined\n    // in the \"action\" attribute of the form when valid\n    submitHandler: function(form) {\n      //form.submit();\n      runCalcs();\n      //alert(\"hello\");\n    }\n  });\n}\n\n</script>\n\n<form action=\"\" name=\"qform\" id=\"qform\">\n<div id=\"section1\"></div>\n\n<div class='section'>\n<button type=\"submit\">Submit</button>\n\n</div>\n\n\n<div class='section'>\n<p><b>Numeric Values and Calculations</b></p>\n<div id=\"calcs\" class=\"notes\"></div>\n\n</div>\n</form>","output":"str","x":490,"y":4740,"wires":[["3b164ec0.e53212","eb87b56c.b4ec18"]],"info":"https://bossanova.uk/jspreadsheet/v4/"},{"id":"66c1eca7.8027f4","type":"inject","z":"cca63413.fec0b8","name":"","topic":"","payload":"{\"title\":\"Heat Network Designer\",\"body\":\"\"}","payloadType":"json","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":190,"y":4740,"wires":[["3dc7abcb.50da24"]]},{"id":"3b164ec0.e53212","type":"change","z":"cca63413.fec0b8","name":"","rules":[{"t":"set","p":"sections.hndesign","pt":"global","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":800,"y":4740,"wires":[[]]},{"id":"eb87b56c.b4ec18","type":"template","z":"cca63413.fec0b8","name":"","field":"heads","fieldType":"msg","format":"handlebars","syntax":"plain","template":"<!doctype html>\n<html lang=\"en\">\n  <head>\n    <!-- Required meta tags -->\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n\n    <!-- Bootstrap CSS -->\n    <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css\" integrity=\"sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2\" crossorigin=\"anonymous\">\n\n   \n    <script src=\"https://kit.fontawesome.com/c6b21b7a8f.js\" crossorigin=\"anonymous\"></script>\n\n \n    <title>Thermal Integration Heatweb Node</title>\n\n\n  <script src=\"https://code.jquery.com/jquery-3.5.1.js\"></script>\n  <script src=\"https://cdn.datatables.net/1.10.22/js/jquery.dataTables.min.js\" type=\"text/javascript\"></script>\n  <link rel=\"stylesheet\" href=\"https://cdn.datatables.net/1.10.22/css/jquery.dataTables.min.css\">\n\n\n  \n    <style>\n    \n    body, textarea, input, select {\n    font-size: 18px;\n}\n\nsection  {\n    border-bottom: 1px solid rgb(255, 255, 255);\n    padding: 20px 50px;\n    /* height: 100vh; */\n    scroll-snap-align: start;\n    /* text-align: center; */\n    position: relative;\n    background: #ffffff;\n    font-size: 18px;\n    }\n\nsection .table {\n            font-size: 16px;\n        }\n\ntable.dataTable.compact tbody td {\n    \n    padding: 2px;\n}   \n\nsection .jstree-node {\n    font-size: 16px;\n}\n\n.nowrap {\n    white-space: nowrap;\n}\n\n@media (max-width: 1480px) {\n\n    section {\n        \n        padding: 20px 20px;\n        \n        }\n\n}\n\n@media (max-width:480px) {\n\n    section {\n        \n        padding: 10px 8px;\n        \n        }\n\n}\n\n    \n        .maxsiz {\n\n        width: 100%; \n        height: 100vh;\n\n        }\n\n        .maxsiz2 {\n\n        width: 100%; \n        height: calc(100vh - 70px);\n\n        }\n\n        .dot {\n            height: 13px;\n            width: 13px;\n            background-color: rgb(52, 58, 52);\n            border-radius: 20%;\n            border: 2px;\n            border-color: rgb(5, 5, 20);\n            display: inline-block;\n        }\n\n        .thindot {\n            height: 13px;\n            width: 6px;\n            background-color: rgb(52, 58, 52);\n            border-radius: 20%;\n            border: 2px;\n            border-color: rgb(5, 5, 20);\n            display: inline-block;\n        }\n\n        .dotspacer {\n            height: 13px;\n            width: 2px;\n            display: inline-block;\n        }\n\n        .navReadings  {\n            /* border-bottom: 1px solid rgb(32, 28, 28); */\n            padding: 20px 50px 0px 50px;\n            /* background: #6d6d70; */\n            font-size: 80%;   \n            /* font-weight: bold; */\n            text-align: left;\n            color: #212529;   \n        }\n\n        .navReadings a {\n            color: #212529;         \n        }\n\n        \n\n           \n    </style>","output":"str","x":740,"y":4680,"wires":[["50dc8f01.495d2"]]},{"id":"50dc8f01.495d2","type":"function","z":"cca63413.fec0b8","name":"","func":"msg.payload = msg.heads + \"<section>\" +  msg.payload.body + \"</section></body></html>\"\n\nreturn msg;","outputs":1,"noerr":0,"x":930,"y":4680,"wires":[["8ef6666b.cb0d08"]]},{"id":"8ef6666b.cb0d08","type":"http response","z":"cca63413.fec0b8","name":"","statusCode":"","headers":{},"x":1130,"y":4680,"wires":[]},{"id":"c2688db4.30369","type":"http in","z":"cca63413.fec0b8","name":"","url":"/ui/qdata","method":"get","upload":false,"swaggerDoc":"","x":210,"y":4820,"wires":[["b7f7b607.f883f8"]]},{"id":"23e0f808.2b2c58","type":"http response","z":"cca63413.fec0b8","name":"","statusCode":"","headers":{"content-type":"application/json"},"x":730,"y":4820,"wires":[]},{"id":"b7f7b607.f883f8","type":"change","z":"cca63413.fec0b8","name":"Heat Network Design","rules":[{"t":"set","p":"payload","pt":"msg","to":"{\"title\":\"My Form\",\"sections\":[{\"title\":\"General\",\"questions\":[{\"title\":\"Number of properties\",\"question\":\"How many properties are there?\",\"id\":\"nProperties\",\"if\":\"true\",\"required\":true,\"type\":\"integer\",\"notes\":\"The total number of properties connected to the heat network.\",\"units\":\"properties\"},{\"title\":\"Number of buildings\",\"question\":\"How many buildings are there?\",\"id\":\"nBuildings\",\"if\":\"true\",\"required\":true,\"type\":\"integer\",\"notes\":\"The total number of buildings connected to the heat network. See https://hw7.ddns.net/ui/svgexplode?svg=HeatNetwork6.svg&display=g16444,g11206,bulkhm,g14636,landlordload,g16320,g5006,bufferstore,lowgrade,heatpump2,heatpump2-2,qton1,fbyp,hiu,g68266,singlemainpump,backupmainpump,g130841-3,g14610-0,g14636-2,g14610,finestrainer,cwsvcs,g14124,g14362.\",\"units\":\"buildings\"},{\"title\":\"Total number of people\",\"question\":\"How many people are there in total?\",\"id\":\"nPeople\",\"if\":\"true\",\"required\":false,\"type\":\"integer\",\"units\":\"people\"},{\"title\":\"DHW network return temperature\",\"question\":\"What is the network return temperature for DHW?\",\"notes\":\"The temperature fed back from a property to the network when running peak load domestic hot water.\",\"id\":\"tPriRtnDHW\",\"if\":\"true\",\"required\":false,\"default\":19,\"type\":\"number\",\"units\":\"°C\"},{\"title\":\"Central heating output per property\",\"question\":\"What is the central heating output per property?\",\"id\":\"kwCHPP\",\"if\":\"true\",\"required\":false,\"type\":\"number\",\"units\":\"kW\"},{\"title\":\"Central heating network return temperature\",\"question\":\"What is the network return temperature for central heating?\",\"notes\":\"The temperature fed back from a property to the network when running peak load central heating.\",\"id\":\"tPriRtnCH\",\"if\":\"{{kwCHPP}}\",\"required\":false,\"default\":45,\"type\":\"number\",\"units\":\"°C\"},{\"title\":\"Central heating diversity\",\"question\":\"What diversity is applied to central heating output?\",\"id\":\"divCH\",\"if\":\"{{kwCHPP}}\",\"required\":false,\"default\":70,\"type\":\"number\",\"units\":\"%\"},{\"title\":\"Peak network flow temperature\",\"question\":\"What is the peak network flow temperature?\",\"id\":\"tPeak\",\"if\":\"true\",\"required\":true,\"default\":80,\"type\":\"number\",\"units\":\"°C\"}]}],\"calculations\":[{\"id\":\"pPP\",\"title\":\"Average people per property\",\"if\":\"nPeople, nProperties\",\"function\":\"{{nPeople}} / {{nProperties}}\",\"units\":\"people\"},{\"id\":\"kwDHWEst\",\"title\":\"Typical HIU rating for DHW\",\"if\":\"{{pPP}}\",\"function\":\"{{pPP}}<3?37.5:({{pPP}}<5?45:60)\",\"units\":\"kW\"},{\"id\":\"peepDS439\",\"title\":\"People per standard DS439 property\",\"if\":\"true\",\"function\":\"2.3\",\"units\":\"people\"},{\"id\":\"eqPropDS439\",\"title\":\"Equivalent number of DS439 properties\",\"if\":\"{{peepDS439}}\",\"function\":\"{{nPeople}}/{{peepDS439}}\",\"units\":\"properties\"},{\"id\":\"kwDS439\",\"title\":\"Peak DHW Load (DS439)\",\"if\":\"{{eqPropDS439}}\",\"function\":\"Math.ceil((1.19* {{eqPropDS439}}) + (18.8* Math.pow({{eqPropDS439}},0.5)) + 17.6)\",\"units\":\"kW\"},{\"id\":\"vPPEST\",\"title\":\"Volume DHW used per property per day\",\"if\":\"true\",\"function\":\"40\",\"units\":\"litres\"},{\"id\":\"vPHEST\",\"title\":\"Volume DHW used per person per day\",\"if\":\"true\",\"function\":\"28\",\"units\":\"litres\"},{\"id\":\"tRiseEST\",\"title\":\"Average temperature Rise on DHW\",\"if\":\"true\",\"function\":\"35\",\"units\":\"°C\"},{\"id\":\"vDHWEST\",\"title\":\"Volume used per day for DHW\",\"if\":\"true\",\"function\":\"({{nProperties}}*{{vPPEST}})+({{nPeople}}*{{vPHEST}})\",\"units\":\"litres\"},{\"id\":\"kwhDHWEST\",\"title\":\"Energy used per day for DHW\",\"if\":\"true\",\"function\":\"({{vDHWEST}} * 4.2 * {{tRiseEST}}) / 3600\",\"units\":\"kWh\"},{\"id\":\"kwCH\",\"title\":\"Peak central heating load\",\"if\":\"true\",\"function\":\"({{kwCHPP}} * {{divCH}} * {{nProperties}}) / 100\",\"units\":\"kW\"},{\"id\":\"kwPeak\",\"title\":\"Peak load\",\"if\":\"true\",\"function\":\"{{kwCH}}+{{kwDS439}}\",\"units\":\"kW\"},{\"id\":\"kwPeak24\",\"title\":\"Average 24h peak load\",\"if\":\"true\",\"function\":\"{{kwCH}}+({{kwhDHWEST}}/24)\",\"units\":\"kW\"},{\"id\":\"m3hDHWPeak\",\"title\":\"Primary flow rate at peak DHW load\",\"if\":\"true\",\"function\":\"3.6 * {{kwDS439}} / (4.2 * ({{tPeak}}-{{tPriRtnDHW}}))\",\"units\":\"m3/h\"},{\"id\":\"m3hCHPeak\",\"title\":\"Primary flow rate at peak central heating load\",\"if\":\"true\",\"function\":\"3.6 * {{kwCH}} / (4.2 * ({{tPeak}}-{{tPriRtnCH}}))\",\"units\":\"m3/h\"},{\"id\":\"m3hPeak\",\"title\":\"Primary flow rate at peak load\",\"if\":\"true\",\"function\":\"{{m3hDHWPeak}}+{{m3hCHPeak}}\",\"units\":\"m3/h\"},{\"id\":\"tPriRtnPeak\",\"title\":\"Primary return temperature at peak load\",\"if\":\"true\",\"function\":\"{{tPeak}} - ({{kwPeak}} / (4.2 * {{m3hPeak}} / 3.6))\",\"units\":\"°C\"}]}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":480,"y":4820,"wires":[["23e0f808.2b2c58"]]}]