The Ultimate Node-RED Thermostat for Home Assistant

NEST style thermostat with: temperature week scheduler, countdown and manual mode

Original projects

This project is an integration of these:

Demo

DemoEN gif 01

Features

This widget allows you to control the climate element of the Home Assistant, expanding its functionality.

  • Three different options for setting up the thermostat:
    • Week Scheduler (with Away mode)
    • Countdown
    • Manual
  • State report through a NEST style thermostat:
    • OFF
    • ON
      • Idle
      • Heat
      • Away mode (that limits the maximum temperature)
  • Chart

Screenshots

Home:

Screenshot home 01

Week Scheduler:

Screenshot scheduler 01

Screenshot scheduler 02

Countdown:

Screenshot countdown 01

Screenshot countdown 02

Manual:

Screenshot manual 01

State report:

Off:

Screenshot state 01

On:

Screenshot state 02

Heat:

Screenshot state 03

Away:

Screenshot state 04

Chart:

Screenshot chart 01

How to install

  1. Install node-red-contrib-mytimeout & node-red-contrib-moment

  2. Download the flow, then go to your node-red application and press import > cliboard and finally select the file downloaded. Two versions available:

  3. Modify the Properties Server (ex: Home Assistant) and Entity Id (ex: climate.termostato) of the following nodes:

    • Climate
    • Away from Alarm
    • svc: climate:set_temperature
    • svc: climate:turn_off
    • svc: climate:turn_on

    These nodes are highlighted in the flow by the comment "Set this" (set_this image).

  4. Deploy and enjoy!

GitHub

Link: The Ultimate Node-RED Thermostat for Home Assistant

Changelog

v3.4

  • Use "let" instead of "var" to work with Node-Red 2.2

v3.3

  • Fix "Messy graphic" in the first startup (#3)
  • Align the time zone settings used in the nodes (#13)
  • Fix a minor countdown bug

v3.2

  • Fix "change state" in countdown

v3.1

  • Fix countdown_followed_by

v3.0

  • Improve the user interface
  • Add Countdown mode
  • Add Manual mode
  • Add Chart
  • Implement 0.1 degree steps in the NEST style thermostat

v1.0

Initial commit

Potential future improvements

  • Schedule mode with 0.1 degree steps
  • Improve the graphics component (currently: only horizontal and no-resizing)
[{"id":"8850e193.e1712","type":"subflow","name":"SaveChart","info":"","category":"","in":[{"x":80,"y":140,"wires":[{"id":"40784bb1.204934"}]}],"out":[{"x":820,"y":120,"wires":[{"id":"a6525d2.cb354a","port":0}]}]},{"id":"a0f72417.ea2838","type":"file","z":"8850e193.e1712","name":"backup","filename":"","appendNewline":false,"createDir":true,"overwriteFile":"true","x":700,"y":160,"wires":[[]]},{"id":"70a2db09.6ff354","type":"file in","z":"8850e193.e1712","name":"restore","filename":"","format":"utf8","sendError":true,"x":530,"y":120,"wires":[["a6525d2.cb354a"]]},{"id":"922d4285.5f7e1","type":"json","z":"8850e193.e1712","name":"","property":"payload","action":"str","pretty":false,"x":530,"y":160,"wires":[["a0f72417.ea2838"]]},{"id":"a6525d2.cb354a","type":"json","z":"8850e193.e1712","name":"","property":"payload","action":"str","pretty":false,"x":690,"y":120,"wires":[[]]},{"id":"40784bb1.204934","type":"function","z":"8850e193.e1712","name":"LoadSave","func":"var strSafe=msg.topic;\nif(strSafe)\n{\n    msg.filename =\"/share/chart.save\";\n    var loaded=context.get(strSafe)||0;\n    \n    if(0===loaded||strSafe==\"restore\")\n    {\n        //node.warn(\"Loading chart\");\n        msg.payload=\"load\";\n        context.set(strSafe,1);\n    }\n    else\n    {\n        //node.warn(\"Writing chart\");\n        //node.warn(msg.payload);\n    }\n    return msg;\n \n}\nreturn null;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":230,"y":140,"wires":[["51fb3038.7afb6"]]},{"id":"51fb3038.7afb6","type":"switch","z":"8850e193.e1712","name":"Load data","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"load","vt":"str"},{"t":"else"}],"checkall":"false","repair":false,"outputs":2,"x":380,"y":140,"wires":[["70a2db09.6ff354"],["922d4285.5f7e1"]]},{"id":"232d7ecc.e5ff62","type":"tab","label":"The Ultimate Thermostat v3.4 ENG - giuseppeg88","disabled":false,"info":"GitHub:\nhttps://github.com/giuseppeg88/node-red-contrib-thermostat-scheduler\n\nLICENSE:\nGNU General Public License v3.0\nPermissions of this strong copyleft license are conditioned on making available complete source code of licensed works and modifications, which include larger works using a licensed work, under the same license. Copyright and license notices must be preserved. Contributors provide an express grant of patent rights."},{"id":"a20813b5.e2d44","type":"ui_template","z":"232d7ecc.e5ff62","group":"77b849cb.094c08","name":"css etc","order":3,"width":0,"height":0,"format":"<style>\n\n.filled { height: 38px !important;padding: 0 !important; margin: 0 !important;}\n.nr-dashboard-template { padding: 0; margin: 0;}\n.rounded { border-radius: 12px 12px 12px 12px;}\n.bigfont { font-size: 18px;}\n.smallfont { font-size: 12px;}\n.thedays { cursor:pointer; vertical-align:bottom; height:38px; }\n.the2px  { background-color: #9E9E9E; height:2px; }\n.theblocks {width:100%; height:0%; background-color:#4CAF50; }\n.greybuttons { background-color:#009688 !important; width:38px; border: none;}\n.thetemps { cursor:pointer; font-size:70%; color:#9E9E9E !important; }\n.smallheadings { color:white; font-size:80%; }\n\n</style>\n\n<script>\nvar current=1;\n\n$('.vibrate').on('click', function() {\n  navigator.vibrate(100);\n});\n\nfunction restore_bg(x) {\n            $(this).css(\"background-color\", x);\n    };\n\n$('.touched').on('mousedown', function() {\n    \n    var x= $(this).css(\"background-color\");\n    $(this).css(\"background-color\", \"yellow\");\n    \n    setTimeout(restore_bg.bind(this,x),50);\n    navigator.vibrate(80);\n    });\n    \n</script>","storeOutMessages":false,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"local","x":220,"y":80,"wires":[[]]},{"id":"6d32710.e54269","type":"ui_template","z":"232d7ecc.e5ff62","group":"77b849cb.094c08","name":"Settings page","order":1,"width":8,"height":5,"format":"<script>\nvar thedays=[\"SUNDAY\",\"MONDAY\",\"TUESDAY\",\"WEDNESDAY\",\"THURSDAY\",\"FRIDAY\",\"SATURDAY\"];\nvar ID = \"AA\";\n\nvar last=29;\n\nfunction bar(mm,val)\n{\nif (val==12 || val==12.5) { $(mm).height(\"12%\"); $(mm).css('background-color', '#1E88E5'); } //blue\nif (val==13 || val==13.5) { $(mm).height(\"16%\"); $(mm).css('background-color', '#039BE5'); }\nif (val==14 || val==14.5) { $(mm).height(\"20%\"); $(mm).css('background-color', '#00ACC1'); } \nif (val==15 || val==15.5) { $(mm).height(\"24%\"); $(mm).css('background-color', '#039BE5'); } // cyan\nif (val==16 || val==16.5) { $(mm).height(\"28%\"); $(mm).css('background-color', '#00ACC1'); }\nif (val==17 || val==17.5) { $(mm).height(\"32%\"); $(mm).css('background-color', '#00897B'); } \nif (val==18 || val==18.5) { $(mm).height(\"36%\"); $(mm).css('background-color', '#388E3C'); } // green\nif (val==19 || val==19.5) { $(mm).height(\"40%\"); $(mm).css('background-color', '#689F38'); }\nif (val==20 || val==20.5) { $(mm).height(\"44%\"); $(mm).css('background-color', '#C0CA33'); }\nif (val==21 || val==21.5) { $(mm).height(\"48%\"); $(mm).css('background-color', '#FDD835'); } // yellow\nif (val==22 || val==22.5) { $(mm).height(\"52%\"); $(mm).css('background-color', '#FBC02D'); }\nif (val==23 || val==23.5) { $(mm).height(\"56%\"); $(mm).css('background-color', '#FFA000'); }\nif (val==24) { $(mm).height(\"60%\"); $(mm).css('background-color','#E64A19'); } // red\n}\n\n//function bar(mm,val)\n//{\n//if (val==14 || val==14.5) { $(mm).height(\"12%\"); $(mm).css('background-color', '#1E88E5'); } //blue\n//if (val==15 || val==15.5) { $(mm).height(\"16%\"); $(mm).css('background-color', '#039BE5'); }\n//if (val==16 || val==16.5) { $(mm).height(\"20%\"); $(mm).css('background-color', '#00ACC1'); } \n//if (val==17 || val==17.5) { $(mm).height(\"24%\"); $(mm).css('background-color', '#039BE5'); } // cyan\n//if (val==18 || val==18.5) { $(mm).height(\"28%\"); $(mm).css('background-color', '#00ACC1'); }\n//if (val==19 || val==19.5) { $(mm).height(\"32%\"); $(mm).css('background-color', '#00897B'); } \n//if (val==20 || val==20.5) { $(mm).height(\"36%\"); $(mm).css('background-color', '#388E3C'); } // green\n//if (val==21 || val==21.5) { $(mm).height(\"40%\"); $(mm).css('background-color', '#689F38'); }\n//if (val==22 || val==22.5) { $(mm).height(\"44%\"); $(mm).css('background-color', '#C0CA33'); }\n//if (val==23 || val==23.5) { $(mm).height(\"48%\"); $(mm).css('background-color', '#FDD835'); } // yellow\n//if (val==24 || val==24.5) { $(mm).height(\"52%\"); $(mm).css('background-color', '#FBC02D'); }\n//if (val==25 || val==25.5) { $(mm).height(\"56%\"); $(mm).css('background-color', '#FFA000'); }\n//if (val==26) { $(mm).height(\"60%\"); $(mm).css('background-color','#E64A19'); } // red\n//}\n\nfunction stat(text)\n{\n$(\"#info\"+ID).text(text);\nvar tm=setTimeout(function(){ $(\"#info\"+ID).text(\"Ok\"); clearTimeout(tm);}, 3000);\n}\n\nfunction selec(val,sta)\n{\nvar w=\"#td\"+val+ID;\n if (sta) $(w).css('background-color','#4CAF50'); else $(w).css('background-color','#FFC107');\n}\n\n    (function(scope){\n        scope.ID = ID;\n        scope.send({payload: '29'})\n        scope.$watch('msg', function(msg) {\n            selec(last,0); last=msg.selector; selec(last,1);\n            for (var x=0; x<24; x++) \n                { \n                    var w=\"#t\"+x+ID; bar(w,msg.timing[((msg.days-1)*24)+x].toFixed(1)); \n                    var v=\"#v\"+x+ID; $(v).text(msg.timing[((msg.days-1)*24)+x].toFixed(1)+\"°\")\n                } \n            for (var x=0; x<2; x++) { var w=\"#s\"+x+ID; $(w).text(msg.timing[168+x].toFixed(1)+\"°\"); }\n             $(\"#d0\"+ID).text(thedays[msg.days-1]);\n             if ((last>4) &&(last<29))\n                 $(\"#current\"+ID).text(msg.timing[((msg.days-1)*24)+last-5].toFixed(1) + \"°\");\n             else\n                 $(\"#current\"+ID).text(\"-\");\n                 \n            if (msg.foryou!=\"\") { stat(msg.foryou);  }\n            \n             \n        });\n \n    })(scope);\n      \n\n</script>\n<table width=\"100%\">\n    \n    <tr>\n        <td colspan=3>&nbsp;</td>\n        <td colspan=6><center><span class=\"smallheadings\" >Day</span></center></td>\n        <!--<td colspan=3><center><span class=\"smallheadings\">Frost</span></center></td> -->\n        <td colspan=3><center><span class=\"smallheadings\">AWAY</span></center></td> \n    </tr>\n   \n    <tr>\n        <td colspan=3>&nbsp;</td>\n        <td ng-click=\"send({payload: '29'})\" colspan=6><center><span id=\"{{ 'd0' + ID }}\" style=\"cursor:pointer;color:white;font-size:100%\">LUNEDI</span></center></td>\n        <!-- <td ng-click=\"send({payload: '1'})\" colspan=3><center><span id=\"{{'s0' + ID }}\" style=\"color:white;font-size:100%\">14</span></center></td>  -->\n        <td ng-click=\"send({payload: '2'})\" colspan=3><center><span id=\"{{'s1' + ID }}\" style=\"cursor:pointer;color:white;font-size:100%\">17</span></center></td> \n    </tr>\n    \n    <tr style=\"height:2px\">\n        <td colspan=3 style=\"background-color:#9E9E9E;height:2px;\"></td> \n        <td id=\"{{ 'td29' + ID }}\" colspan=6 style=\"background-color:#9E9E9E;height:2px;\"></td>        \n        <!-- <td id=\"{{'td1' + ID }}\" colspan=3 style=\"background-color:#9E9E9E;height:2px;\"></td> -->\n        <td id=\"{{'td2 + ID '}}\" colspan=3 style=\"background-color:#9E9E9E;height:2px;\"></td> \n    </tr>  \n\n    <tr>\n        <td ng-click=\"send({payload: '5'})\" class=\"thedays\"><span id=\"{{ 'v0' + ID }}\" class=\"thetemps\"></span><div id=\"{{ 't0' + ID }}\" class=\"theblocks\"></div></td>\n        <td ng-click=\"send({payload: '6'})\" class=\"thedays\"><span id=\"{{ 'v1' + ID }}\" class=\"thetemps\"></span><div id=\"{{ 't1' + ID }}\" class=\"theblocks\"></div></td>\n        <td ng-click=\"send({payload: '7'})\" class=\"thedays\"><span id=\"{{ 'v2' + ID }}\" class=\"thetemps\"></span><div id=\"{{ 't2' + ID }}\" class=\"theblocks\"></div></td>\n        <td ng-click=\"send({payload: '8'})\" class=\"thedays\"><span id=\"{{ 'v3' + ID }}\" class=\"thetemps\"></span><div id=\"{{ 't3' + ID }}\" class=\"theblocks\"></div></td>\n        <td ng-click=\"send({payload: '9'})\" class=\"thedays\"><span id=\"{{ 'v4' + ID }}\" class=\"thetemps\"></span><div id=\"{{ 't4' + ID }}\" class=\"theblocks\"></div></td>\n        <td ng-click=\"send({payload: '10'})\" class=\"thedays\"><span id=\"{{ 'v5' + ID }}\" class=\"thetemps\"></span><div id=\"{{ 't5' + ID }}\" class=\"theblocks\"></div></td>\n        <td ng-click=\"send({payload: '11'})\" class=\"thedays\"><span id=\"{{ 'v6' + ID }}\" class=\"thetemps\"></span><div id=\"{{ 't6' + ID }}\" class=\"theblocks\"></div></td>\n        <td ng-click=\"send({payload: '12'})\" class=\"thedays\"><span id=\"{{ 'v7' + ID }}\" class=\"thetemps\"></span><div id=\"{{ 't7' + ID }}\" class=\"theblocks\"></div></td>\n        <td ng-click=\"send({payload: '13'})\" class=\"thedays\"><span id=\"{{ 'v8' + ID }}\" class=\"thetemps\"></span><div id=\"{{ 't8' + ID }}\" class=\"theblocks\"></div></td>\n        <td ng-click=\"send({payload: '14'})\" class=\"thedays\"><span id=\"{{ 'v9' + ID }}\" class=\"thetemps\"></span><div id=\"{{ 't9' + ID }}\" class=\"theblocks\"></div></td>\n        <td ng-click=\"send({payload: '15'})\" class=\"thedays\"><span id=\"{{ 'v10' + ID }}\" class=\"thetemps\"></span><div id=\"{{ 't10' + ID }}\" class=\"theblocks\"></div></td>\n        <td ng-click=\"send({payload: '16'})\" class=\"thedays\"><span id=\"{{ 'v11' + ID }}\" class=\"thetemps\"></span><div id=\"{{ 't11' + ID }}\" class=\"theblocks\"></div></td>\n    </tr>  \n    \n    <tr style=\"height:2px\">\n        <td id=\"{{ 'td5' + ID }}\" class=\"the2px\"></td>\n        <td id=\"{{ 'td6' + ID }}\" class=\"the2px\"></td>\n        <td id=\"{{ 'td7' + ID }}\" class=\"the2px\"></td>\n        <td id=\"{{ 'td8' + ID }}\" class=\"the2px\"></td>\n        <td id=\"{{ 'td9' + ID }}\" class=\"the2px\"></td>\n        <td id=\"{{ 'td10' + ID }}\" class=\"the2px\"></td>\n        <td id=\"{{ 'td11' + ID }}\" class=\"the2px\"></td>\n        <td id=\"{{ 'td12' + ID }}\" class=\"the2px\"></td>\n        <td id=\"{{ 'td13' + ID }}\" class=\"the2px\"></td>\n        <td id=\"{{ 'td14' + ID }}\" class=\"the2px\"></td>\n        <td id=\"{{ 'td15' + ID }}\" class=\"the2px\"></td>\n        <td id=\"{{ 'td16' + ID }}\" class=\"the2px\"></td>\n    </tr>   \n    \n    <tr>\n        <td align=\"center\">0</td>\n        <td align=\"center\">1</td>\n        <td align=\"center\">2</td>\n        <td align=\"center\">3</td>\n        <td align=\"center\">4</td>\n        <td align=\"center\">5</td>\n        <td align=\"center\">6</td>\n        <td align=\"center\">7</td>\n        <td align=\"center\">8</td>\n        <td align=\"center\">9</td>\n        <td align=\"center\">10</td>\n        <td align=\"center\">11</td>\n    </tr> \n    \n    <tr>\n        <td ng-click=\"send({payload: '17'})\" class=\"thedays\"><span id=\"{{ 'v12' + ID }}\" class=\"thetemps\"></span><div id=\"{{ 't12' + ID }}\" class=\"theblocks\"></div></td>\n        <td ng-click=\"send({payload: '18'})\" class=\"thedays\"><span id=\"{{ 'v13' + ID }}\" class=\"thetemps\"></span><div id=\"{{ 't13' + ID }}\" class=\"theblocks\"></div></td>\n        <td ng-click=\"send({payload: '19'})\" class=\"thedays\"><span id=\"{{ 'v14' + ID }}\" class=\"thetemps\"></span><div id=\"{{ 't14' + ID }}\" class=\"theblocks\"></div></td>\n        <td ng-click=\"send({payload: '20'})\" class=\"thedays\"><span id=\"{{ 'v15' + ID }}\" class=\"thetemps\"></span><div id=\"{{ 't15' + ID }}\" class=\"theblocks\"></div></td>\n        <td ng-click=\"send({payload: '21'})\" class=\"thedays\"><span id=\"{{ 'v16' + ID }}\" class=\"thetemps\"></span><div id=\"{{ 't16' + ID }}\" class=\"theblocks\"></div></td>\n        <td ng-click=\"send({payload: '22'})\" class=\"thedays\"><span id=\"{{ 'v17' + ID }}\" class=\"thetemps\"></span><div id=\"{{ 't17' + ID }}\" class=\"theblocks\"></div></td>\n        <td ng-click=\"send({payload: '23'})\" class=\"thedays\"><span id=\"{{ 'v18' + ID }}\" class=\"thetemps\"></span><div id=\"{{ 't18' + ID }}\" class=\"theblocks\"></div></td>\n        <td ng-click=\"send({payload: '24'})\" class=\"thedays\"><span id=\"{{ 'v19' + ID }}\" class=\"thetemps\"></span><div id=\"{{ 't19' + ID }}\" class=\"theblocks\"></div></td>\n        <td ng-click=\"send({payload: '25'})\" class=\"thedays\"><span id=\"{{ 'v20' + ID }}\" class=\"thetemps\"></span><div id=\"{{ 't20' + ID }}\" class=\"theblocks\"></div></td>\n        <td ng-click=\"send({payload: '26'})\" class=\"thedays\"><span id=\"{{ 'v21' + ID }}\" class=\"thetemps\"></span><div id=\"{{ 't21' + ID }}\" class=\"theblocks\"></div></td>\n        <td ng-click=\"send({payload: '27'})\" class=\"thedays\"><span id=\"{{ 'v22' + ID }}\" class=\"thetemps\"></span><div id=\"{{ 't22' + ID }}\" class=\"theblocks\"></div></td>\n        <td ng-click=\"send({payload: '28'})\" class=\"thedays\"><span id=\"{{ 'v23' + ID }}\" class=\"thetemps\"></span><div id=\"{{ 't23' + ID }}\" class=\"theblocks\"></div></td>\n    </tr>  \n    \n    <tr style=\"height:2px\">\n        <td id=\"{{ 'td17' + ID }}\" class=\"the2px\"></td>\n        <td id=\"{{ 'td18' + ID }}\" class=\"the2px\"></td>\n        <td id=\"{{ 'td19' + ID }}\" class=\"the2px\"></td>\n        <td id=\"{{ 'td20' + ID }}\" class=\"the2px\"></td>\n        <td id=\"{{ 'td21' + ID }}\" class=\"the2px\"></td>\n        <td id=\"{{ 'td22' + ID }}\" class=\"the2px\"></td>\n        <td id=\"{{ 'td23' + ID }}\" class=\"the2px\"></td>\n        <td id=\"{{ 'td24' + ID }}\" class=\"the2px\"></td>\n        <td id=\"{{ 'td25' + ID }}\" class=\"the2px\"></td>\n        <td id=\"{{ 'td26' + ID }}\" class=\"the2px\"></td>\n        <td id=\"{{ 'td27' + ID }}\" class=\"the2px\"></td>\n        <td id=\"{{ 'td28' + ID }}\" class=\"the2px\"></td>\n    </tr>  \n    \n    <tr>\n        <td align=\"center\">12</td>\n        <td align=\"center\">13</td>\n        <td align=\"center\">14</td>\n        <td align=\"center\">15</td>\n        <td align=\"center\">16</td>\n        <td align=\"center\">17</td>\n        <td align=\"center\">18</td>\n        <td align=\"center\">19</td>\n        <td align=\"center\">20</td>\n        <td align=\"center\">21</td>\n        <td align=\"center\">22</td>\n        <td align=\"center\">23</td>\n    </tr> \n    \n    <tr height=\"5px\">\n        <td colspan=\"12\"></td>\n    </tr>\n    \n    <tr height=\"20px\"> \n        <td colspan=2 class=\"smallheadings\"><center> Status: </center></td> \n\n        <td colspan=10 ><center><span id=\"{{ 'info' + ID }}\" class=\"smallheadings\" ></span></center></td>\n\n    </tr>\n    \n    <tr height=\"5px\">\n        <td colspan=\"12\"></td>\n    </tr>\n\n    <tr style=\"height:48px\">\n        <td colspan=2> <center>\n            <button class=\"vibrate filled touched smallfont rounded greybuttons\" ng-click=\"send({payload: 'd'})\"> \n               <ng-md-icon style=\"color: #fff;\" icon=\"keyboard_arrow_down\">\n                    <!--<md-tooltip md-direction=\"bottom\">DECREASE</md-tooltip>-->\n                </ng-md-icon>\n            </button> </center>\n        </td>\n        \n        <td colspan=2><center><span id=\"{{ 'current' + ID }}\" style=\"color:white;font-size:120%\"> - </span></center></td>\n\n        <td colspan=2> <center>\n            <button class=\"vibrate filled touched smallfont rounded greybuttons\" ng-click=\"send({payload: 'u'})\"> \n               <ng-md-icon style=\"color: #fff;\" icon=\"keyboard_arrow_up\">\n                    <!--<md-tooltip md-direction=\"bottom\">INCREASE</md-tooltip>-->\n                </ng-md-icon>\n            </button> </center>\n        </td>\n\n        <td colspan=2> <center>\n            <button class=\"vibrate filled touched smallfont rounded greybuttons\" ng-click=\"send({payload: 'r'})\"> \n               <ng-md-icon style=\"color: #fff;\" icon=\"content_copy\">\n                    <!--<md-tooltip md-direction=\"bottom\">COPY</md-tooltip>-->\n                </ng-md-icon>\n            </button> </center>\n        </td>\n\n\n        <td colspan=2> <center>\n            <button class=\"vibrate filled touched smallfont rounded greybuttons\" ng-click=\"send({payload: 's'})\"> \n                 <ng-md-icon style=\"color: #fff;\" icon=\"save\">\n                    <!--<md-tooltip md-direction=\"bottom\">SAVE</md-tooltip>-->\n                </ng-md-icon>\n            </button> </center>\n        </td>\n\n        <td colspan=2> <center>\n            <button class=\"vibrate filled touched smallfont rounded greybuttons\"  ng-click=\"send({payload: 'c'})\"> \n                 <ng-md-icon style=\"color: #fff;\" icon=\"cancel\">\n                    <!--<md-tooltip md-direction=\"bottom\">CANCEL</md-tooltip>-->\n                </ng-md-icon>\n            </button> </center>\n        </td>\n    </tr>\n\n</table>","storeOutMessages":false,"fwdInMessages":false,"resendOnRefresh":false,"templateScope":"local","x":250,"y":140,"wires":[["dd34a.ad5d5cb68"]]},{"id":"dd34a.ad5d5cb68","type":"function","z":"232d7ecc.e5ff62","name":"Process controls","func":"if ( typeof context.days == 'undefined' || flow.get(\"resetday\")) \n{\n    var date = new Date();\n    var daynum = date.getDay();\n    context.days= daynum+1;\n    flow.set(\"resetday\",false);\n}\n\nif ( typeof (flow.get(\"thermostat_mode\")) == 'undefined') \n{\n    flow.set(\"thermostat_mode\",\"off\");\n}\n\nif ( typeof context.selector == 'undefined' ) context.selector=29;\nif ( typeof context.saving == 'undefined' ) context.saving=1;\n\nlet timing = global.get(\"timing\");\nif ( !timing || !Array.isArray(timing) ) \n    {\n        timing=[\n                14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,\n                14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,\n                14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,\n                14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,\n                14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,\n                14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,\n                14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,\n                6,14\n                ];\n        context.saving=1;                    \n        global.set(\"timing\",timing);\n    }\n//if ( typeof global.get(\"days\") != 'undefined' ) context.days=global.get(\"days\");\n\nswitch (msg.payload)\n    {\n    case \"u\" :  if ((context.selector>4)&&(context.selector<29))\n                {\n                    timing[((context.days-1)*24)+context.selector-5]+=0.5; \n                    if (timing[((context.days-1)*24)+context.selector-5]>24){ timing[((context.days-1)*24)+context.selector-5]=24; msg.foryou=\"Hit limit\";}\n                    else msg.foryou=\"Incremented temperature\";\n                }\n                if (context.selector==1) { if (timing[168+(context.selector-1)]<24) {  timing[168+(context.selector-1)]+=0.5; msg.foryou=\"Incremented temperature setting 0.5 degree\"; } else msg.foryou=\"Hit limit\"; }\n                if (context.selector==2) { if (timing[168+(context.selector-1)]<24) {  timing[168+(context.selector-1)]+=0.5; msg.foryou=\"Incremented away setting 0.5 degree\"; } else msg.foryou=\"Hit limit\"; }\n                if (context.selector==29) { msg.foryou=\"Forward one day\"; context.days++; if (context.days>7) { context.days = 1; } }\n                break;\n    case \"d\" :  if ((context.selector>4)&&(context.selector<29))\n                {\n                    timing[((context.days-1)*24)+context.selector-5]-=0.5; \n                    if (timing[((context.days-1)*24)+context.selector-5]<14){ timing[((context.days-1)*24)+context.selector-5]=14; msg.foryou=\"Hit limit\";}\n                    else msg.foryou=\"Decremented temperature\";\n                }\n                if (context.selector==1) { if (timing[168+(context.selector-1)]>12) { timing[168+(context.selector-1)]-=0.5; msg.foryou=\"Decremented temperature setting 0.5 degree\"; } else msg.foryou=\"Hit limit\"; }\n                if (context.selector==2) { if (timing[168+(context.selector-1)]>12) { timing[168+(context.selector-1)]-=0.5; msg.foryou=\"Decremented away setting 0.5 degree\"; } else msg.foryou=\"Hit limit\"; }\n                if (context.selector==29) {  msg.foryou=\"Backward one day\";  context.days--; if (context.days < 1) { context.days = 7;  } }\n                break;\n    case 'r' :  if ((context.selector>=5)&&(context.selector<28))\n                        {\n                         timing[((context.days-1)*24)+context.selector-4]=timing[((context.days-1)*24)+context.selector-5];   \n                         context.selector++;\n                         msg.foryou=\"Copied settings to the next hour slot\";\n                        }\n                if ((context.selector==29)&&(context.days<7))\n                        {\n                         for (var a=0;a<24;a++)\n                            {\n                             timing[((context.days)*24)+a]=timing[((context.days-1)*24)+a];   \n                            }\n                         context.days++;\n                         msg.foryou=\"Copied a complete day's settings\";\n                        }\n                        else if(context.days==7)\n                            msg.foryou=\"End of days!!\";\n                break;\n    case 's': context.saving=0;  msg.foryou=\"Settings saved to disk\"; break;\n    case '1':\n    case '2':\n    case '3':\n    case '4':\n    case '5':\n    case '6':\n    case '7':\n    case '8':\n    case '9':\n    case '10':\n    case '11':\n    case '12':\n    case '13':\n    case '14':\n    case '15':\n    case '16':\n    case '17':\n    case '18':\n    case '19':\n    case '20':\n    case '21':\n    case '22':\n    case '23':\n    case '24':\n    case '25':\n    case '26':\n    case '27':\n    case '28':\n    case '29': context.selector=parseInt(msg.payload);\n               if (msg.payload=='1') msg.foryou=\"Temperature selection\"; \n               else if (msg.payload=='2') msg.foryou=\"Away temperature selection\";\n               else if (msg.payload=='29') msg.foryou=\"Day selection\";\n               else  msg.foryou=\"Selected hour period \" + (parseInt(msg.payload)-5);\n    break;\n    case 'c' : msg.payload=\"anything\";  msg.foryou=\"Changes cancelled\"; node.send([null,null,msg]);\n    }\n\nmsg.temperatures=context.temperatures;\nmsg.timing=timing;\nmsg.days=context.days;\nmsg.selector=context.selector;\n\nnode.send([msg,null,null]);\n\nif (context.saving===0) \n    { \n       msg.topic=\"\";\n        msg.timing=\"\";\n        msg.payload=JSON.stringify(timing);\n        node.send([null,msg,null]); \n        context.saving=1;\n    }\n    msg.foryou=\"\"","outputs":"3","noerr":0,"initialize":"","finalize":"","libs":[],"x":520,"y":200,"wires":[["3ef8b3bc.ac9c4c"],["9f4b5142.fdf5f"],["75ae9da9.269474"]]},{"id":"2830b85e.b46e28","type":"inject","z":"232d7ecc.e5ff62","name":"Once only","props":[{"p":"payload","v":"","vt":"str"},{"p":"topic","v":"","vt":"string"}],"repeat":"","crontab":"","once":true,"topic":"","payload":"","payloadType":"str","x":258,"y":237,"wires":[["dd34a.ad5d5cb68","5a560577.28db5c"]]},{"id":"405952d1.88024c","type":"inject","z":"232d7ecc.e5ff62","name":"Every minute","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"60","crontab":"","once":true,"onceDelay":"","topic":"","payload":"","payloadType":"str","x":280,"y":480,"wires":[["51527af2.8a4bc4","1c644b05.149ac5"]]},{"id":"7e5cbc2c.ec6844","type":"function","z":"232d7ecc.e5ff62","name":"Process heat","func":"if (flow.get(\"thermostat_mode\") != \"schedule\") {\n    return null;\n}\n\nlet timing=global.get(\"timing\");\nvar now = new Date(msg.time);\n\nvar fixed_temperature = timing[(now.getDay()*24)+now.getHours()];\nvar away_temperature = timing[169];\nvar away_mode = flow.get(\"away_mode\");\n\nif (away_mode) {\n    msg.payload = (fixed_temperature > away_temperature ? away_temperature : fixed_temperature);\n    msg.awayMode = true;\n} else {\n    msg.payload = fixed_temperature;\n    msg.awayMode = false;\n}\n\nnode.status({fill:\"blue\",shape:\"dot\",text: \"Actual: \" + flow.get(\"ambient_temperature\") + \" - Set point: \" + msg.payload + \"°C\" + \" [away mode: \" + away_mode + \"]\"});\n\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":850,"y":480,"wires":[["448c69c5.b711b8"]]},{"id":"9f4b5142.fdf5f","type":"file","z":"232d7ecc.e5ff62","name":"backup","filename":"/share/thermo_scheduler.save","appendNewline":true,"createDir":true,"overwriteFile":"true","x":710,"y":200,"wires":[[]]},{"id":"5a560577.28db5c","type":"file in","z":"232d7ecc.e5ff62","name":"Restore","filename":"/share/thermo_scheduler.save","format":"utf8","sendError":true,"x":490,"y":280,"wires":[["4df5e512.83b18c"]]},{"id":"4df5e512.83b18c","type":"function","z":"232d7ecc.e5ff62","name":"Restore data from SD","func":"let timing = global.get(\"timing\");\ntiming=JSON.parse(msg.payload);\nglobal.set(\"timing\",timing);\nmsg.payload=\"\";\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":710,"y":280,"wires":[["7084552d.d7481c"]]},{"id":"abc0b579.1afcf8","type":"moment","z":"232d7ecc.e5ff62","name":"","topic":"","input":"payload","inputType":"msg","inTz":"ETC/UTC","adjAmount":"0","adjType":"hours","adjDir":"add","format":"dddd DD MMMM YYYY - HH:mm","locale":"en_EN","output":"payload","outputType":"msg","outTz":"ETC/UTC","x":580,"y":560,"wires":[["83bfaecb.915b7"]]},{"id":"83bfaecb.915b7","type":"ui_text","z":"232d7ecc.e5ff62","group":"317e9d9c.dea8e2","order":2,"width":6,"height":1,"name":"Time","label":"","format":"{{msg.payload}}","layout":"col-center","x":830,"y":560,"wires":[]},{"id":"225f4215.1e3c2e","type":"link in","z":"232d7ecc.e5ff62","name":"restore","links":["75ae9da9.269474"],"x":311.5,"y":288,"wires":[["5a560577.28db5c"]]},{"id":"75ae9da9.269474","type":"link out","z":"232d7ecc.e5ff62","name":"restore","links":["225f4215.1e3c2e","93466115.e953e"],"x":671.5,"y":236,"wires":[]},{"id":"93895d89.5dca4","type":"link in","z":"232d7ecc.e5ff62","name":"restoreSD","links":["7084552d.d7481c","8e1afd82.fc23f","aa45f621.310328"],"x":311,"y":189,"wires":[["dd34a.ad5d5cb68"]]},{"id":"7084552d.d7481c","type":"link out","z":"232d7ecc.e5ff62","name":"restoreSD","links":["93895d89.5dca4"],"x":905,"y":280,"wires":[]},{"id":"3ef8b3bc.ac9c4c","type":"link out","z":"232d7ecc.e5ff62","name":"setSettings","links":["174712b3.605f0d"],"x":672.5,"y":160,"wires":[]},{"id":"174712b3.605f0d","type":"link in","z":"232d7ecc.e5ff62","name":"setSettings","links":["3ef8b3bc.ac9c4c"],"x":117.5,"y":137,"wires":[["6d32710.e54269"]]},{"id":"51527af2.8a4bc4","type":"moment","z":"232d7ecc.e5ff62","name":"time","topic":"","input":"","inputType":"date","inTz":"ETC/UTC","adjAmount":"0","adjType":"hours","adjDir":"add","format":"","locale":"en_EN","output":"time","outputType":"msg","outTz":"ETC/UTC","x":570,"y":480,"wires":[["7e5cbc2c.ec6844"]]},{"id":"f7a38f63.75e4e","type":"function","z":"232d7ecc.e5ff62","name":"Store away state","func":"msg.topic = 'awayMode';\nvar awayMode = false;\n\nif(msg.payload == \"armed_away\"){\n    awayMode = true; \n}\n\nflow.set(\"away_mode\",awayMode);\nnode.status({fill:\"red\",shape:\"dot\",text:\"away mode: \" + awayMode});  \n\nreturn msg;\n","outputs":1,"noerr":0,"x":470,"y":780,"wires":[["c7d21b05.6295e8"]]},{"id":"448c69c5.b711b8","type":"api-call-service","z":"232d7ecc.e5ff62","name":"","server":"41108723.0bf848","version":3,"debugenabled":false,"service_domain":"climate","service":"set_temperature","entityId":"climate.termostato","data":"{\"temperature\": \"{{payload}}\", \"hvac_mode\": \"heat\"}","dataType":"json","mergecontext":"","mustacheAltTags":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"data"}],"queue":"none","x":1120,"y":560,"wires":[[]]},{"id":"425b16f.0ff6de8","type":"api-call-service","z":"232d7ecc.e5ff62","name":"","server":"41108723.0bf848","version":3,"service_domain":"climate","service":"turn_on","entityId":"climate.termostato","data":"{\"entity_id\": \"climate.termostato\"}","dataType":"json","mergecontext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":860,"y":1020,"wires":[[]]},{"id":"6493b049.0ffdb","type":"api-call-service","z":"232d7ecc.e5ff62","name":"","server":"41108723.0bf848","version":3,"service_domain":"climate","service":"turn_off","entityId":"climate.termostato","data":"{\"entity_id\": \"climate.termostato\"}","dataType":"json","mergecontext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":860,"y":960,"wires":[[]]},{"id":"218d1883.54c6a8","type":"server-state-changed","z":"232d7ecc.e5ff62","name":"Away from Alarm","server":"41108723.0bf848","version":3,"entityidfilter":"alarm_control_panel.allarme_casa","entityidfiltertype":"exact","outputinitially":true,"state_type":"str","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":true,"for":"0","forType":"num","forUnits":"minutes","outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":220,"y":780,"wires":[["f7a38f63.75e4e"]]},{"id":"e79d9b45.1e8478","type":"inject","z":"232d7ecc.e5ff62","name":"Every second","props":[{"p":"payload","v":"","vt":"date"},{"p":"topic","v":"","vt":"string"}],"repeat":"1","crontab":"","once":true,"onceDelay":"","topic":"","payload":"","payloadType":"date","x":280,"y":560,"wires":[["abc0b579.1afcf8"]]},{"id":"a2da96c9.f211d8","type":"ui_template","z":"232d7ecc.e5ff62","group":"317e9d9c.dea8e2","name":"Nest","order":4,"width":5,"height":5,"format":"<div id=\"thermostat\"></div>\n\n<style>\n\n@import url(https://fonts.googleapis.com/css?family=Open+Sans:300);\n\n#thermostat {\n margin: 0 0;\n overflow: hidden;\n -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\n\n.dial {\n    -webkit-user-select: none;\n    -moz-user-select: none;\n    -ms-user-select: none;\n    user-select: none;\n}\n.dial.off .dial__ico__leaf {\n    visibility: hidden;\n}\n.dial.off .dial__lbl--target {\n    visibility: hidden;\n}\n.dial.off .dial__lbl--target--half {\n    visibility: hidden;\n}\n.dial.off .dial__lbl--off {\n    opacity: 1;\n}\n.dial .dial__shape {\n    -webkit-transition: fill 0.5s;\n    transition: fill 0.5s;\n}\n.dial path.dial__ico__leaf {\n    fill: #13EB13;\n    opacity: 0;\n    -webkit-transition: opacity 0.5s;\n    transition: opacity 0.5s;\n    pointer-events: none;\n}\n.dial.has-leaf .dial__ico__leaf {\n    display: block;\n    opacity: 1;\n    pointer-events: initial;\n}\n.dial__editableIndicator {\n    fill-rule: evenodd;\n    opacity: 0;\n    -webkit-transition: opacity 0.5s;\n    transition: opacity 0.5s;\n}\n.dial--edit path.dial__editableIndicator {\n    fill: white;\n}\n.dial--edit .dial__editableIndicator {\n    opacity: 1;\n}\n.dial--state--off .dial__shape {\n    fill: #3d3c3c;\n}\n.dial--state--idle .dial__shape {\n    fill: #3d3c3c;\n}\n.dial--state--heating .dial__shape {\n    fill: #E36304;\n}\n.dial--state--cooling .dial__shape {\n    fill: #007AF1;\n}\n.dial .dial__ticks path {\n    fill: rgba(255, 255, 255, 0.3);\n}\n.dial .dial__ticks path.active {\n    fill: rgba(255, 255, 255, 0.8);\n}\n.dial text {\n    fill: white;\n    text-anchor: middle;\n    font-family: Helvetica, sans-serif;\n    alignment-baseline: central;\n}\n.dial__lbl--target {\n    font-size: 120px;\n    font-weight: bold;\n}\n.dial__lbl--target--half {\n    font-size: 40px;\n    font-weight: bold;\n    opacity: 0;\n    -webkit-transition: opacity 0.1s;\n    transition: opacity 0.1s;\n}\n.dial__lbl--target--half.shown {\n    opacity: 1;\n    -webkit-transition: opacity 0s;\n    transition: opacity 0s;\n}\n.dial__lbl--ambient {\n    font-size: 22px;\n    font-weight: bold;\n}\n.dial__lbl--off {\n    font-size: 72px;\n    font-weight: bold;\n    opacity: 0;\n    pointer-events: none;\n}\n#controls {\n    font-family: Open Sans;\n    background-color: rgba(255, 255, 255, 0.25);\n    padding: 20px;\n    border-radius: 5px;\n    position: absolute;\n    left: 50%;\n    -webkit-transform: translatex(-50%);\n    transform: translatex(-50%);\n    margin-top: 20px;\n}\n#controls label {\n    text-align: left;\n    display: block;\n}\n#controls label span {\n    display: inline-block;\n    width: 200px;\n    text-align: right;\n    font-size: 0.8em;\n    text-transform: uppercase;\n}\n#controls p {\n    margin: 0;\n    margin-bottom: 1em;\n    padding-bottom: 1em;\n    border-bottom: 2px solid #ccc;\n}\n</style>\n<script>\n    var thermostatDial = (function() {\n\t\n\t/*\n\t * Utility functions\n\t */\n\t\n\t// Create an element with proper SVG namespace, optionally setting its attributes and appending it to another element\n\tfunction createSVGElement(tag,attributes,appendTo) {\n\t\tvar element = document.createElementNS('http://www.w3.org/2000/svg',tag);\n\t\tattr(element,attributes);\n\t\tif (appendTo) {\n\t\t\tappendTo.appendChild(element);\n\t\t}\n\t\treturn element;\n\t}\n\t\n\t// Set attributes for an element\n\tfunction attr(element,attrs) {\n\t\tfor (var i in attrs) {\n\t\t\telement.setAttribute(i,attrs[i]);\n\t\t}\n\t}\n\t\n\t// Rotate a cartesian point about given origin by X degrees\n\tfunction rotatePoint(point, angle, origin) {\n\t\tvar radians = angle * Math.PI/180;\n\t\tvar x = point[0]-origin[0];\n\t\tvar y = point[1]-origin[1];\n\t\tvar x1 = x*Math.cos(radians) - y*Math.sin(radians) + origin[0];\n\t\tvar y1 = x*Math.sin(radians) + y*Math.cos(radians) + origin[1];\n\t\treturn [x1,y1];\n\t}\n\t\n\t// Rotate an array of cartesian points about a given origin by X degrees\n\tfunction rotatePoints(points, angle, origin) {\n\t\treturn points.map(function(point) {\n\t\t\treturn rotatePoint(point, angle, origin);\n\t\t});\n\t}\n\t\n\t// Given an array of points, return an SVG path string representing the shape they define\n\tfunction pointsToPath(points) {\n\t\treturn points.map(function(point, iPoint) {\n\t\t\treturn (iPoint>0?'L':'M') + point[0] + ' ' + point[1];\n\t\t}).join(' ')+'Z';\n\t}\n\t\n\tfunction circleToPath(cx, cy, r) {\n\t\treturn [\n\t\t\t\"M\",cx,\",\",cy,\n\t\t\t\"m\",0-r,\",\",0,\n\t\t\t\"a\",r,\",\",r,0,1,\",\",0,r*2,\",\",0,\n\t\t\t\"a\",r,\",\",r,0,1,\",\",0,0-r*2,\",\",0,\n\t\t\t\"z\"\n\t\t].join(' ').replace(/\\s,\\s/g,\",\");\n\t}\n\t\n\tfunction donutPath(cx,cy,rOuter,rInner) {\n\t\treturn circleToPath(cx,cy,rOuter) + \" \" + circleToPath(cx,cy,rInner);\n\t}\n\t\n\t// Restrict a number to a min + max range\n\tfunction restrictToRange(val,min,max) {\n\t\tif (val < min) return min;\n\t\tif (val > max) return max;\n\t\treturn val;\n\t}\n\t\n\t// Round a number to the nearest 0.5\n\t//function roundHalf(num) {\n\t//\treturn Math.round(num*10)/10;\n\t//}\n\t\n\t// Round a number to tenth's 0.1\n\tfunction roundHalf(num) {\n\t\treturn Math.round(num*10)/10;\n\t}\n\t\n\tfunction setClass(el, className, state) {\n\t\tel.classList[state ? 'add' : 'remove'](className);\n\t}\n\t\n\t/*\n\t * The \"MEAT\"\n\t */\n\n\treturn function(targetElement, options) {\n\t\tvar self = this;\n\t\t\n\t\t/*\n\t\t * Options\n\t\t */\n\t\toptions = options || {};\n\t\toptions = {\n\t\t\tdiameter: options.diameter || 380,\n\t\t\tminValue: options.minValue || 14, // Minimum value for target temperature\n\t\t\tmaxValue: options.maxValue || 24, // Maximum value for target temperature\n\t\t\tnumTicks: options.numTicks || 200, // Number of tick lines to display around the dial\n\t\t\tonSetTargetTemperature: options.onSetTargetTemperature || function() {}, // Function called when new target temperature set by the dial\n\t\t};\n\t\t\n\t\t/*\n\t\t * Properties - calculated from options in many cases\n\t\t */\n\t\tvar properties = {\n\t\t\ttickDegrees: 300, // Degrees of the dial that should be covered in tick lines\n\t\t\trangeValue: options.maxValue - options.minValue,\n\t\t\tradius: options.diameter/2,\n\t\t\tticksOuterRadius: options.diameter / 30,\n\t\t\tticksInnerRadius: options.diameter / 8,\n\t\t\thvac_states: ['idle', 'off', 'heating', 'cooling'],\n\t\t\tdragLockAxisDistance: 15,\n\t\t}\n\t\tproperties.lblAmbientPosition = [properties.radius, properties.ticksOuterRadius-(properties.ticksOuterRadius-properties.ticksInnerRadius)/2]\n\t\tproperties.offsetDegrees = 180-(360-properties.tickDegrees)/2;\n\t\t\n\t\t/*\n\t\t * Object state\n\t\t */\n\t\tvar state = {\n\t\t\ttarget_temperature: options.minValue,\n\t\t\tambient_temperature: options.minValue,\n\t\t\thvac_state: properties.hvac_states[0],\n\t\t\thas_leaf: false,\n\t\t\toff: false\n\t\t};\n\t\t\n\t\t/*\n\t\t * Property getter / setters\n\t\t */\n\t\tObject.defineProperty(this,'target_temperature',{\n\t\t\tget: function() {\n\t\t\t\treturn state.target_temperature;\n\t\t\t},\n\t\t\tset: function(val) {\n\t\t\t\tstate.target_temperature = restrictTargetTemperature(+val);\n\t\t\t\trender();\n\t\t\t}\n\t\t});\n\t\tObject.defineProperty(this,'ambient_temperature',{\n\t\t\tget: function() {\n\t\t\t\treturn state.ambient_temperature;\n\t\t\t},\n\t\t\tset: function(val) {\n\t\t\t\tstate.ambient_temperature = roundHalf(+val);\n\t\t\t\trender();\n\t\t\t}\n\t\t});\n\t\tObject.defineProperty(this,'hvac_state',{\n\t\t\tget: function() {\n\t\t\t\treturn state.hvac_state;\n\t\t\t},\n\t\t\tset: function(val) {\n\t\t\t\tif (properties.hvac_states.indexOf(val)>=0) {\n\t\t\t\t\tstate.hvac_state = val;\n\t\t\t\t\trender();\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\tfunction str2bool(strvalue){\n          return (strvalue && typeof strvalue == 'string') ? (strvalue.toLowerCase() == 'true') : (strvalue == true);\n        }\n\t\tObject.defineProperty(this,'has_leaf',{\n\t\t\tget: function() {\n\t\t\t\treturn state.has_leaf;\n\t\t\t},\n\t\t\tset: function(val) {\n\t\t\t\tstate.has_leaf = !!str2bool(val);\n\t\t\t\trender();\n\t\t\t}\n\t\t});\n\t\tObject.defineProperty(this,'off',{\n\t\t\tget: function() {\n\t\t\t\treturn state.off;\n\t\t\t},\n\t\t\tset: function(val) {\n\t\t\t\tstate.off = !!str2bool(val);\n\t\t\t\trender();\n\t\t\t}\n\t\t});\n\t\t\n\t\t/*\n\t\t * SVG\n\t\t */\n\t\tvar svg = createSVGElement('svg',{\n\t\t\twidth: '95%', //options.diameter+'px',\n\t\t\theight: '95%', //options.diameter+'px',\n\t\t\tviewBox: '0 0 '+options.diameter+' '+options.diameter,\n\t\t\tclass: 'dial'\n\t\t},targetElement);\n\t\t// CIRCULAR DIAL\n\t\tvar circle = createSVGElement('circle',{\n\t\t\tcx: properties.radius,\n\t\t\tcy: properties.radius,\n\t\t\tr: properties.radius,\n\t\t\tclass: 'dial__shape'\n\t\t},svg);\n\t\t// EDITABLE INDICATOR\n\t\tvar editCircle = createSVGElement('path',{\n\t\t\td: donutPath(properties.radius,properties.radius,properties.radius-4,properties.radius-8),\n\t\t\tclass: 'dial__editableIndicator',\n\t\t},svg);\n\t\t\n\t\t/*\n\t\t * Ticks\n\t\t */\n\t\tvar ticks = createSVGElement('g',{\n\t\t\tclass: 'dial__ticks'\t\n\t\t},svg);\n\t\tvar tickPoints = [\n\t\t\t[properties.radius-1, properties.ticksOuterRadius],\n\t\t\t[properties.radius+1, properties.ticksOuterRadius],\n\t\t\t[properties.radius+1, properties.ticksInnerRadius],\n\t\t\t[properties.radius-1, properties.ticksInnerRadius]\n\t\t];\n\t\tvar tickPointsLarge = [\n\t\t\t[properties.radius-1.5, properties.ticksOuterRadius],\n\t\t\t[properties.radius+1.5, properties.ticksOuterRadius],\n\t\t\t[properties.radius+1.5, properties.ticksInnerRadius+20],\n\t\t\t[properties.radius-1.5, properties.ticksInnerRadius+20]\n\t\t];\n\t\tvar theta = properties.tickDegrees/options.numTicks;\n\t\tvar tickArray = [];\n\t\tfor (var iTick=0; iTick<options.numTicks; iTick++) {\n\t\t\ttickArray.push(createSVGElement('path',{d:pointsToPath(tickPoints)},ticks));\n\t\t};\n\t\t\n\t\t/*\n\t\t * Labels\n\t\t */\n\t\tvar lblTarget = createSVGElement('text',{\n\t\t\tx: properties.radius,\n\t\t\ty: properties.radius,\n\t\t\tclass: 'dial__lbl dial__lbl--target'\n\t\t},svg);\n\t\tvar lblTarget_text = document.createTextNode('');\n\t\tlblTarget.appendChild(lblTarget_text);\n\t\t//\n\t\tvar lblTargetHalf = createSVGElement('text',{\n\t\t\tx: properties.radius + properties.radius/2.5,\n\t\t\ty: properties.radius - properties.radius/8,\n\t\t\tclass: 'dial__lbl dial__lbl--target--half'\n\t\t},svg);\n\t\tvar lblTargetHalf_text = document.createTextNode('5');\n\t\tlblTargetHalf.appendChild(lblTargetHalf_text);\n\t\t//\n\t\tvar lblAmbient = createSVGElement('text',{\n\t\t\tclass: 'dial__lbl dial__lbl--ambient'\n\t\t},svg);\n\t\tvar lblAmbient_text = document.createTextNode('');\n\t\tlblAmbient.appendChild(lblAmbient_text);\n\t\t//\n\t\tvar lbloff = createSVGElement('text',{\n\t\t\tx: properties.radius,\n\t\t\ty: properties.radius,\n\t\t\tclass: 'dial__lbl dial__lbl--off'\n\t\t},svg);\n\t\tvar lbloff_text = document.createTextNode('OFF');\n\t\tlbloff.appendChild(lbloff_text);\n\t\t//\n\t\tvar icoLeaf = createSVGElement('path',{\n\t\t\tclass: 'dial__ico__leaf'\n\t\t},svg);\n\t\t\n\t\t/*\n\t\t * LEAF\n\t\t */\n\t\tvar leafScale = properties.radius/5/100;\n\t\tvar leafDef = [\"M\", 3, 84, \"c\", 24, 17, 51, 18, 73, -6, \"C\", 100, 52, 100, 22, 100, 4, \"c\", -13, 15, -37, 9, -70, 19, \"C\", 4, 32, 0, 63, 0, 76, \"c\", 6, -7, 18, -17, 33, -23, 24, -9, 34, -9, 48, -20, -9, 10, -20, 16, -43, 24, \"C\", 22, 63, 8, 78, 3, 84, \"z\"].map(function(x) {\n\t\t\treturn isNaN(x) ? x : x*leafScale;\n\t\t}).join(' ');\n\t\tvar translate = [properties.radius-(leafScale*100*0.5),properties.radius*1.5]\n\t\tvar icoLeaf = createSVGElement('path',{\n\t\t\tclass: 'dial__ico__leaf',\n\t\t\td: leafDef,\n\t\t\ttransform: 'translate('+translate[0]+','+translate[1]+')'\n\t\t},svg);\n\t\t\t\n\t\t/*\n\t\t * RENDER\n\t\t */\n\t\tfunction render() {\n\t\t\trenderOff();\n\t\t\trenderHvacState();\n\t\t\trenderTicks();\n\t\t\trenderTargetTemperature();\n\t\t\trenderAmbientTemperature();\n\t\t\trenderLeaf();\n\t\t}\n\t\trender();\n\n\t\t/*\n\t\t * RENDER - ticks\n\t\t */\n\t\tfunction renderTicks() {\n\t\t\tvar vMin, vMax;\n\t\t\tif (self.off) {\n\t\t\t\tvMin = self.ambient_temperature;\n\t\t\t\tvMax = vMin;\n\t\t\t} else {\n\t\t\t\tvMin = Math.min(self.ambient_temperature, self.target_temperature);\n\t\t\t\tvMax = Math.max(self.ambient_temperature, self.target_temperature);\n\t\t\t}\n\t\t\tvar min = restrictToRange(Math.round((vMin-options.minValue)/properties.rangeValue * options.numTicks),0,options.numTicks-1);\n\t\t\tvar max = restrictToRange(Math.round((vMax-options.minValue)/properties.rangeValue * options.numTicks),0,options.numTicks-1);\n\t\t\t//\n\t\t\ttickArray.forEach(function(tick,iTick) {\n\t\t\t\tvar isLarge = iTick==min || iTick==max;\n\t\t\t\tvar isActive = iTick >= min && iTick <= max;\n\t\t\t\tattr(tick,{\n\t\t\t\t\td: pointsToPath(rotatePoints(isLarge ? tickPointsLarge: tickPoints,iTick*theta-properties.offsetDegrees,[properties.radius, properties.radius])),\n\t\t\t\t\tclass: isActive ? 'active' : ''\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\t\n\t\t/*\n\t\t * RENDER - ambient temperature\n\t\t */\n\t\tfunction renderAmbientTemperature() {\n\t\t\tlblAmbient_text.nodeValue = self.ambient_temperature;\n\t\t\tvar peggedValue = restrictToRange(self.ambient_temperature, options.minValue, options.maxValue);\n\t\t\tdegs = properties.tickDegrees * (peggedValue-options.minValue)/properties.rangeValue - properties.offsetDegrees;\n\t\t\tif (peggedValue > self.target_temperature) {\n\t\t\t\tdegs += 8;\n\t\t\t} else {\n\t\t\t\tdegs -= 8;\n\t\t\t}\n\t\t\tvar pos = rotatePoint(properties.lblAmbientPosition,degs,[properties.radius, properties.radius]);\n\t\t\tattr(lblAmbient,{\n\t\t\t\tx: pos[0],\n\t\t\t\ty: pos[1]\n\t\t\t});\n\t\t}\n\n\t\t/*\n\t\t * RENDER - target temperature\n\t\t */\n\t\tfunction renderTargetTemperature() {\n\t\t\tlblTarget_text.nodeValue = Math.floor(self.target_temperature);\n\t\t\tsetClass(lblTargetHalf, 'shown', true);\n\t\t\tlblTargetHalf_text.nodeValue = (self.target_temperature % 1).toFixed(1).substring(2);\n\t\t}\n\t\t\n\t\t/*\n\t\t * RENDER - leaf\n\t\t */\n\t\tfunction renderLeaf() {\n\t\t\tsetClass(svg,'has-leaf',self.has_leaf);\n\t\t}\n\t\t\n\t\t/*\n\t\t * RENDER - HVAC state\n\t\t */\n\t\tfunction renderHvacState() {\n\t\t\tArray.prototype.slice.call(svg.classList).forEach(function(c) {\n\t\t\t\tif (c.match(/^dial--state--/)) {\n\t\t\t\t\tsvg.classList.remove(c);\n\t\t\t\t};\n\t\t\t});\n\t\t\tsvg.classList.add('dial--state--'+self.hvac_state);\n\t\t}\n\t\t\n\t\t/*\n\t\t * RENDER - off\n\t\t */\n\t\tfunction renderOff() {\n\t\t\tsvg.classList[self.off ? 'add' : 'remove']('off');\n\t\t}\n\t\t\n\t\t/*\n\t\t * Drag to control\n\t\t */\n\t\tvar _drag = {\n\t\t\tinProgress: false,\n\t\t\tstartPoint: null,\n\t\t\tstartTemperature: 0,\n\t\t\tlockAxis: undefined\n\t\t};\n\t\t\n\t\tfunction eventPosition(ev) {\n\t\t\tif (ev.targetTouches && ev.targetTouches.length) {\n\t\t\t\treturn [ev.targetTouches[0].clientX, ev.targetTouches[0].clientY];\n\t\t\t} else {\n\t\t\t\treturn [ev.x, ev.y];\n\t\t\t};\n\t\t}\n\t\t\n\t\tvar startDelay;\n\t\tfunction dragStart(ev) {\n\t\t\tstartDelay = setTimeout(function() {\n\t\t\t\tsetClass(svg, 'dial--edit', true);\n\t\t\t\t_drag.inProgress = true;\n\t\t\t\t_drag.startPoint = eventPosition(ev);\n\t\t\t\t_drag.startTemperature = self.target_temperature || options.minValue;\n\t\t\t\t_drag.lockAxis = undefined;\n\t\t\t},1000);\n\t\t};\n\t\t\n\t\tfunction dragEnd (ev) {\n\t\t\tclearTimeout(startDelay);\n\t\t\tsetClass(svg, 'dial--edit', false);\n\t\t\tif (!_drag.inProgress) return;\n\t\t\t_drag.inProgress = false;\n\t\t\tif (self.target_temperature != _drag.startTemperature) {\n\t\t\t\tif (typeof options.onSetTargetTemperature == 'function') {\n\t\t\t\t\toptions.onSetTargetTemperature(self.target_temperature);\n\t\t\t\t};\n\t\t\t};\n\t\t};\n\t\t\n\t\tfunction dragMove(ev) {\n\t\t\tev.preventDefault();\n\t\t\tif (!_drag.inProgress) return;\n\t\t\tvar evPos = eventPosition(ev);\n\t\t\tvar dy = _drag.startPoint[1]-evPos[1];\n\t\t\tvar dx = evPos[0] - _drag.startPoint[0];\n\t\t\tvar dxy;\n\t\t\tif (_drag.lockAxis == 'x') {\n\t\t\t\tdxy = dx;\n\t\t\t} else if (_drag.lockAxis == 'y') {\n\t\t\t\tdxy = dy;\n\t\t\t} else if (Math.abs(dy) > properties.dragLockAxisDistance) {\n\t\t\t\t_drag.lockAxis = 'y';\n\t\t\t\tdxy = dy;\n\t\t\t} else if (Math.abs(dx) > properties.dragLockAxisDistance) {\n\t\t\t\t_drag.lockAxis = 'x';\n\t\t\t\tdxy = dx;\n\t\t\t} else {\n\t\t\t\tdxy = (Math.abs(dy) > Math.abs(dx)) ? dy : dx;\n\t\t\t};\n\t\t\tvar dValue = (dxy*getSizeRatio())/(options.diameter)*properties.rangeValue;\n\t\t\tself.target_temperature = roundHalf(_drag.startTemperature+dValue);\n\t\t}\n\t\t\n\t\tsvg.addEventListener('mousedown',dragStart);\n\t\tsvg.addEventListener('touchstart',dragStart);\n\t\t\n\t\tsvg.addEventListener('mouseup',dragEnd);\n\t\tsvg.addEventListener('mouseleave',dragEnd);\n\t\tsvg.addEventListener('touchend',dragEnd);\n\t\t\n\t\tsvg.addEventListener('mousemove',dragMove);\n\t\tsvg.addEventListener('touchmove',dragMove);\n\t\t//\n\t\t\n\t\t/*\n\t\t * Helper functions\n\t\t */\n\t\tfunction restrictTargetTemperature(t) {\n\t\t\treturn restrictToRange(roundHalf(t),options.minValue,options.maxValue);\n\t\t}\n\t\t\n\t\tfunction angle(point) {\n\t\t\tvar dx = point[0] - properties.radius;\n\t\t\tvar dy = point[1] - properties.radius;\n\t\t\tvar theta = Math.atan(dx/dy) / (Math.PI/180);\n\t\t\tif (point[0]>=properties.radius && point[1] < properties.radius) {\n\t\t\t\ttheta = 90-theta - 90;\n\t\t\t} else if (point[0]>=properties.radius && point[1] >= properties.radius) {\n\t\t\t\ttheta = 90-theta + 90;\n\t\t\t} else if (point[0]<properties.radius && point[1] >= properties.radius) {\n\t\t\t\ttheta = 90-theta + 90;\n\t\t\t} else if (point[0]<properties.radius && point[1] < properties.radius) {\n\t\t\t\ttheta = 90-theta+270;\n\t\t\t}\n\t\t\treturn theta;\n\t\t};\n\t\t\n\t\tfunction getSizeRatio() {\n\t\t\treturn options.diameter / targetElement.clientWidth;\n\t\t}\n\t\t\n\t};\n})();\n\n/* ==== */\nvar initializing = true;\n\n(function(scope) {\n    var nest = new thermostatDial(document.getElementById('thermostat'),{\n    \tonSetTargetTemperature: function(v) {\n    \t    var p = {\n    \t        \"ambient_temperature\":nest.ambient_temperature,\n    \t        \"target_temperature\":v,\n    \t        \"hvac_state\":nest.hvac_state,\n    \t        \"has_leaf\": nest.has_leaf,\n    \t        \"off_view\":nest.off\n    \t    };\n    \t\tscope.send({topic: \"target_temperature\", payload: p});\n    \t}\n    });\n    \n    scope.$watch('msg', function(data) {\n        if (initializing) {\n            initializing = false;\n            } else {\n            nest.ambient_temperature = data.payload.ambient_temperature || 0;\n            nest.target_temperature = data.payload.target_temperature || 0;\n            nest.hvac_state = data.payload.hvac_state || \"off\";\n            nest.has_leaf = data.payload.has_leaf || false;\n            nest.off = data.payload.off_view || false;\n        }\n        \n    });\n})(scope);\n\n</script>","storeOutMessages":true,"fwdInMessages":false,"resendOnRefresh":true,"templateScope":"local","x":590,"y":640,"wires":[["55922061.dfac1"]]},{"id":"c7d21b05.6295e8","type":"function","z":"232d7ecc.e5ff62","name":"Data","func":"msg.topic = \"update\";\n\nvar msg2 = {};\nmsg2.payload = flow.get(\"thermostat_mode\");\n\nvar data = {\n    'ambient_temperature': flow.get(\"ambient_temperature\") || 20,\n    'target_temperature': flow.get(\"target_temperature\") || 14,\n    'hvac_state': flow.get(\"hvac_action\") || 'off',\n    'has_leaf': flow.get(\"away_mode\") || false,\n    'off_view': flow.get(\"off_view\") || false\n}\nmsg.payload = data;\nnode.status({fill:\"red\",shape:\"dot\",text: \"ambient_temperature: \" + data.ambient_temperature + \" / target_temperature: \" + data.target_temperature + \" / away_mode: \" + data.has_leaf + \" / climate_state: \" + flow.get(\"climate_state\") + \" / hvac_state: \" + data.hvac_state + \" / thermostat_mode: \" + msg2.payload});\n\nvar msg3 = {};\nif (msg2.payload == \"off\")\n{\n    msg3.payload = false;\n}\nelse if (msg2.payload == \"countdown\")\n{\n    msg3.payload = true;\n}\nelse\n{\n    msg3 = null;\n}\n\nreturn [msg,msg2,msg3];","outputs":3,"noerr":0,"initialize":"","finalize":"","x":710,"y":740,"wires":[["a2da96c9.f211d8","774f3a76.e31b24"],["a76c2176.067ef","8085802d.c81a1"],["f253cf24.005e7"]]},{"id":"55922061.dfac1","type":"function","z":"232d7ecc.e5ff62","name":"New setpoint from UI","func":"if (msg.topic == \"target_temperature\") {\n    msg.payload = msg.payload.target_temperature;\n    flow.set(\"target_temperature\", msg.payload);\n    node.status({fill:\"red\",shape:\"dot\",text:\"target_temperature: \" + msg.payload}); \n    return msg;\n}\n","outputs":1,"noerr":0,"x":820,"y":640,"wires":[["448c69c5.b711b8"]]},{"id":"19f00918.470677","type":"server-state-changed","z":"232d7ecc.e5ff62","name":"Climate","server":"41108723.0bf848","version":3,"entityidfilter":"climate.termostato","entityidfiltertype":"exact","outputinitially":true,"state_type":"str","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":false,"for":"0","forType":"num","forUnits":"minutes","outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":190,"y":720,"wires":[["8dac65c6.991758","1131c41b.b85c3c"]]},{"id":"774f3a76.e31b24","type":"debug","z":"232d7ecc.e5ff62","name":"climate_debug","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":900,"y":740,"wires":[]},{"id":"8dac65c6.991758","type":"function","z":"232d7ecc.e5ff62","name":"Store climate state","func":"msg.topic = 'climate';\n\nflow.set(\"ambient_temperature\", msg.data.new_state.attributes.current_temperature);\nflow.set(\"target_temperature\",msg.data.new_state.attributes.temperature);\nflow.set(\"climate_state\",msg.payload);\nflow.set(\"off_view\",msg.payload == \"off\" ? true : false);\nflow.set(\"hvac_action\",msg.data.new_state.attributes.hvac_action);\n\nnode.status({fill:\"red\",shape:\"dot\",text:msg.payload});  \n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":470,"y":720,"wires":[["c7d21b05.6295e8"]]},{"id":"ee06c229.ddbad","type":"ui_switch","z":"232d7ecc.e5ff62","name":"","label":"","tooltip":"","group":"317e9d9c.dea8e2","order":3,"width":1,"height":1,"passthru":false,"decouple":"true","topic":"","style":"","onvalue":"true","onvalueType":"bool","onicon":"","oncolor":"","offvalue":"false","offvalueType":"bool","officon":"","offcolor":"","x":390,"y":1000,"wires":[["f253cf24.005e7"]]},{"id":"f253cf24.005e7","type":"switch","z":"232d7ecc.e5ff62","name":"","property":"payload","propertyType":"msg","rules":[{"t":"false"},{"t":"true"}],"checkall":"true","repair":false,"outputs":2,"x":550,"y":1000,"wires":[["6493b049.0ffdb","3465588b.53b6a8"],["425b16f.0ff6de8"]]},{"id":"1131c41b.b85c3c","type":"function","z":"232d7ecc.e5ff62","name":"sync switch","func":"var msg2 = {};\n\nif (msg.payload == \"off\"){\n    msg.payload = false;\n    flow.set(\"thermostat_mode\",\"off\");\n    msg2.payload = \"off\";\n}\nelse {\n    msg.payload = true;\n    if (flow.get(\"thermostat_mode\") == \"off\") {\n        flow.set(\"thermostat_mode\",\"schedule\");\n        msg2.payload = \"schedule\";\n    }\n    else {\n        msg2.payload = flow.get(\"thermostat_mode\");\n    }\n}\n\nreturn [msg, msg2];\n","outputs":2,"noerr":0,"initialize":"","finalize":"","x":170,"y":960,"wires":[["ee06c229.ddbad"],["a76c2176.067ef"]]},{"id":"7fb2ff3a.21586","type":"ui_button","z":"232d7ecc.e5ff62","name":"","group":"77b849cb.094c08","order":2,"width":8,"height":1,"passthru":false,"label":"Back","tooltip":"","color":"{{colour}}","bgcolor":"{{payload}}","icon":"","payload":"{\"group\":{\"hide\":[\"Cronotermostato_Programmazione\"],\"show\":[\"Cronotermostato_Nest\"]}}","payloadType":"json","topic":"","x":570,"y":1340,"wires":[["831777b7.320428","430451a2.192e5"]]},{"id":"831777b7.320428","type":"ui_ui_control","z":"232d7ecc.e5ff62","name":"","events":"all","x":860,"y":1360,"wires":[[]]},{"id":"b477c9f6.0170b8","type":"ui_chart","z":"232d7ecc.e5ff62","name":"","group":"eaea6ba8.f76288","order":1,"width":8,"height":5,"label":"Chronological chart","chartType":"line","legend":"true","xformat":"HH:mm","interpolate":"bezier","nodata":"empty","dot":false,"ymin":"","ymax":"","removeOlder":"6","removeOlderPoints":"","removeOlderUnit":"3600","cutout":0,"useOneColor":false,"useUTC":false,"colors":["#d62728","#1f77b4","#ff7f0e","#2ca02c","#98df8a","#aec7e8","#ff9896","#9467bd","#c5b0d5"],"outputs":1,"x":650,"y":2100,"wires":[["2d6074a.6b6708c","5229968d.edc638"]]},{"id":"507b1a54.1c88f4","type":"function","z":"232d7ecc.e5ff62","name":"Data to chart","func":"var msg1 = {};\nmsg1.topic = \"target\";\nmsg1.payload = flow.get(\"target_temperature\");\n\nvar msg2 = {};\nmsg2.topic = \"ambient\";\nmsg2.payload = flow.get(\"ambient_temperature\");\n\nreturn [msg1, msg2];","outputs":2,"noerr":0,"x":410,"y":2100,"wires":[["b477c9f6.0170b8"],["b477c9f6.0170b8"]]},{"id":"2c53d991.929c66","type":"ui_button","z":"232d7ecc.e5ff62","name":"","group":"eaea6ba8.f76288","order":2,"width":8,"height":1,"passthru":false,"label":"Back","tooltip":"","color":"{{colour}}","bgcolor":"{{payload}}","icon":"","payload":"{\"group\":{\"hide\":[\"Cronotermostato_Grafico\"],\"show\":[\"Cronotermostato_Nest\"]}}","payloadType":"json","topic":"","x":570,"y":1440,"wires":[["831777b7.320428","430451a2.192e5"]]},{"id":"8b178bee.f3d4d8","type":"ui_button","z":"232d7ecc.e5ff62","name":"","group":"317e9d9c.dea8e2","order":1,"width":1,"height":1,"passthru":false,"label":"","tooltip":"","color":"{{colour}}","bgcolor":"transparent","icon":"fa-area-chart","payload":"{\"group\":{\"show\":[\"Cronotermostato_Grafico\"],\"hide\":[\"Cronotermostato_Nest\"]}}","payloadType":"json","topic":"","x":570,"y":1380,"wires":[["831777b7.320428","16606c74.6b1b84"]]},{"id":"2d76c115.380b0e","type":"inject","z":"232d7ecc.e5ff62","name":"Every minute","props":[{"p":"payload","v":"","vt":"str"},{"p":"topic","v":"","vt":"string"}],"repeat":"60","crontab":"","once":true,"onceDelay":"","topic":"","payload":"","payloadType":"str","x":200,"y":2100,"wires":[["507b1a54.1c88f4","44d0f73.4fc4a08"]]},{"id":"2d6074a.6b6708c","type":"subflow:8850e193.e1712","z":"232d7ecc.e5ff62","name":"","env":[],"x":830,"y":2100,"wires":[["c78d004c.61678"]]},{"id":"16606c74.6b1b84","type":"function","z":"232d7ecc.e5ff62","name":"restore chart","func":"msg.topic=\"restore\"\nreturn msg;","outputs":1,"noerr":0,"x":810,"y":1520,"wires":[[]]},{"id":"5229968d.edc638","type":"debug","z":"232d7ecc.e5ff62","name":"chart_debug","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":830,"y":2040,"wires":[]},{"id":"9b7353be.d9a39","type":"function","z":"232d7ecc.e5ff62","name":"show today","func":"flow.set(\"resetday\",true);\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":830,"y":1280,"wires":[["831777b7.320428"]]},{"id":"7aea5967.e70758","type":"change","z":"232d7ecc.e5ff62","name":"","rules":[{"t":"set","p":"yves","pt":"global","to":"payload[0]","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1180,"y":2100,"wires":[[]]},{"id":"c78d004c.61678","type":"switch","z":"232d7ecc.e5ff62","name":"","property":"payload","propertyType":"msg","rules":[{"t":"istype","v":"undefined","vt":"undefined"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":990,"y":2100,"wires":[[],["7aea5967.e70758"]]},{"id":"44d0f73.4fc4a08","type":"change","z":"232d7ecc.e5ff62","name":"global.yves","rules":[{"t":"set","p":"payload","pt":"msg","to":"yves","tot":"global"}],"action":"","property":"","from":"","to":"","reg":false,"x":410,"y":2160,"wires":[["b477c9f6.0170b8"]]},{"id":"fe734a7b.67d5f8","type":"ui_button","z":"232d7ecc.e5ff62","name":"","group":"e767a130.9c455","order":7,"width":2,"height":1,"passthru":true,"label":"Stop","tooltip":"","color":"","bgcolor":"","icon":"fa-clock-o","payload":"{ \"payload\": \"stop\" }","payloadType":"str","topic":"","x":330,"y":1680,"wires":[["d4de26bd.02f898"]]},{"id":"40a37775.f9fb38","type":"function","z":"232d7ecc.e5ff62","name":"Seconds to minutes & refresh state","func":"var msg2 = {};\nmsg2.payload = msg.payload;\n\nvar msg3 = {};\n\nvar minutes = msg.payload /60;\nmsg.payload=minutes;\n\nif (msg.state > 0)\n{\n    if (flow.get(\"thermostat_mode\") != 'countdown')\n    {\n        flow.set(\"thermostat_mode\", \"countdown\");\n    }\n    else\n    {\n        msg2 = null;\n    }\n    \n    if (minutes < 1)\n    {\n        msg3.payload = \"\" + (minutes*60).toFixed(0) + \" seconds remaining\";\n    }\n    else\n    {\n        msg3.payload = \"\" + minutes.toFixed(0) + \" minutes remaining\";\n    }\n}\nelse if (msg2.payload == -1 || (msg2.payload == 0 && flow.get(\"countdown_preflag\") != 'off'))\n{\n    flow.set(\"thermostat_mode\", flow.get(\"countdown_followed_by\"));\n}\nelse\n{\n    msg2 = null;\n}\n\nflow.set(\"countdown_preflag\", msg.flag);\n\nreturn [msg, msg2, msg3];","outputs":3,"noerr":0,"initialize":"","finalize":"","x":980,"y":1920,"wires":[["c65c3af1.5254c8"],["3fdfe0fb.4473c"],["ef938785.813cd8"]]},{"id":"c65c3af1.5254c8","type":"ui_gauge","z":"232d7ecc.e5ff62","name":"Countdown Timer","group":"e767a130.9c455","order":1,"width":6,"height":4,"gtype":"donut","title":"","label":"Minutes","format":"{{value | number:0}}","min":0,"max":"60","colors":["#ff2600","#e6e600","#00f900"],"seg1":"","seg2":"","x":1190,"y":1800,"wires":[]},{"id":"e755ecc3.6e6e2","type":"function","z":"232d7ecc.e5ff62","name":"Minutes to seconds","func":"var seconds = msg.payload * 60;\n\nif(seconds==0) \n{\n    msg.payload= \"stop\";\n    return msg;\n}\n\nmsg.payload=\"{\\\"payload\\\": \\\"on\\\", \\\"timeout\\\":\" + seconds + \", \\\"warning\\\": 30}\"\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":410,"y":1880,"wires":[["c2c42385.eacfe"]]},{"id":"2f52248.f6d46dc","type":"ui_slider","z":"232d7ecc.e5ff62","name":"","label":"Minutes","tooltip":" Countdown initiated on release","group":"e767a130.9c455","order":6,"width":6,"height":1,"passthru":true,"outs":"end","topic":"","min":"0","max":"120","step":1,"x":200,"y":1840,"wires":[["e755ecc3.6e6e2","4871ab11.5bd544"]]},{"id":"2e7cbcf.8c07644","type":"ui_toast","z":"232d7ecc.e5ff62","position":"top left","displayTime":"1","highlight":"#e83a2c","sendall":true,"outputs":0,"ok":"OK","cancel":"","raw":false,"topic":"","name":"","x":920,"y":1840,"wires":[]},{"id":"c2c42385.eacfe","type":"mytimeout","z":"232d7ecc.e5ff62","name":"Timer 1-120 Minutes","outtopic":"","outsafe":"","outwarning":"The Countdown is almost over","outunsafe":"The countdown ended!","warning":"","timer":"","debug":false,"ndebug":true,"ignoreCase":false,"repeat":false,"again":false,"x":680,"y":1880,"wires":[["2e7cbcf.8c07644"],["40a37775.f9fb38"]]},{"id":"4871ab11.5bd544","type":"function","z":"232d7ecc.e5ff62","name":"Set min&max","func":"var minuti = msg.payload;\n\nif(minuti==0) \n{\n    minuti=1;\n}\n\nmsg.ui_control = {\"min\":0,\"max\":minuti}\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":390,"y":1800,"wires":[["c65c3af1.5254c8"]]},{"id":"d4de26bd.02f898","type":"function","z":"232d7ecc.e5ff62","name":"Reset Slider","func":"msg.payload = 0;\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":530,"y":1680,"wires":[["2f52248.f6d46dc"]]},{"id":"c964fcab.cd19c","type":"ui_button","z":"232d7ecc.e5ff62","name":"","group":"e767a130.9c455","order":8,"width":8,"height":1,"passthru":false,"label":"Back","tooltip":"","color":"{{colour}}","bgcolor":"{{payload}}","icon":"","payload":"{\"group\":{\"hide\":[\"Cronotermostato_Countdown\"],\"show\":[\"Cronotermostato_Nest\"]}}","payloadType":"json","topic":"","x":570,"y":1500,"wires":[["831777b7.320428","430451a2.192e5"]]},{"id":"a76c2176.067ef","type":"ui_dropdown","z":"232d7ecc.e5ff62","name":"","label":"","tooltip":"","place":"Select mode","group":"317e9d9c.dea8e2","order":5,"width":3,"height":1,"passthru":false,"multiple":false,"options":[{"label":"OFF","value":"off","type":"str"},{"label":"Schedule","value":"schedule","type":"str"},{"label":"Countdown","value":"countdown","type":"str"},{"label":"Manual","value":"manual","type":"str"}],"payload":"","topic":"","x":240,"y":1100,"wires":[["65218d1.af92274","8085802d.c81a1"]]},{"id":"65218d1.af92274","type":"function","z":"232d7ecc.e5ff62","name":"Mode selection","func":"switch (msg.payload){\ncase 'off':\n    flow.set(\"thermostat_mode\",\"off\")\n    return [msg,null,null,null]\n\ncase 'schedule':\n    msg.payload = {\"group\":{\"show\":[\"Cronotermostato_Programmazione\"],\"hide\":[\"Cronotermostato_Nest\"]}};\n    flow.set(\"thermostat_mode\",\"schedule\")\n    return [null,msg,null,null]\n\ncase 'countdown':\n    msg.payload = {\"group\":{\"show\":[\"Cronotermostato_Countdown\"],\"hide\":[\"Cronotermostato_Nest\"]}};\n    return [null,null,msg,null]\n\ncase 'manual':\n    flow.set(\"thermostat_mode\",\"manual\")\n    return [null,null,null,msg]\n}\n\nreturn [null,null,null,null];","outputs":4,"noerr":0,"initialize":"","finalize":"","x":460,"y":1100,"wires":[["6493b049.0ffdb","3465588b.53b6a8"],["9b7353be.d9a39","425b16f.0ff6de8","3465588b.53b6a8"],["831777b7.320428","c5e54bf4.696278"],["425b16f.0ff6de8","3465588b.53b6a8","c218687b.20aa58"]],"outputLabels":["off","schedule","countdown","manual"]},{"id":"11942362.43008d","type":"ui_text","z":"232d7ecc.e5ff62","group":"317e9d9c.dea8e2","order":7,"width":3,"height":3,"name":"","label":"","format":"{{msg.payload}}","layout":"row-left","x":870,"y":1600,"wires":[]},{"id":"e5f159e4.88e208","type":"ui_button","z":"232d7ecc.e5ff62","name":"","group":"317e9d9c.dea8e2","order":6,"width":3,"height":1,"passthru":false,"label":"{{msg.label}}","tooltip":"","color":"","bgcolor":"transparent","icon":"","payload":"setting_link","payloadType":"flow","topic":"","x":550,"y":1200,"wires":[["831777b7.320428","9b7353be.d9a39"]]},{"id":"1852bec.bed1941","type":"ui_dropdown","z":"232d7ecc.e5ff62","name":"","label":"","tooltip":"","place":"After","group":"e767a130.9c455","order":4,"width":2,"height":1,"passthru":false,"multiple":false,"options":[{"label":"OFF","value":"off","type":"str"},{"label":"Schedule","value":"schedule","type":"str"}],"payload":"","topic":"","x":1020,"y":1140,"wires":[["e9cd724f.96163"]]},{"id":"922f2aed.242688","type":"ui_text","z":"232d7ecc.e5ff62","group":"e767a130.9c455","order":3,"width":2,"height":1,"name":"","label":"Followed by","format":"{{msg.payload}}","layout":"col-center","x":1030,"y":1100,"wires":[]},{"id":"430451a2.192e5","type":"link out","z":"232d7ecc.e5ff62","name":"RefreshNest","links":["fc43585a.d453c8"],"x":835,"y":1440,"wires":[]},{"id":"fc43585a.d453c8","type":"link in","z":"232d7ecc.e5ff62","name":"RefreshNest","links":["430451a2.192e5","3fdfe0fb.4473c"],"x":615,"y":820,"wires":[["c7d21b05.6295e8"]]},{"id":"8d623664.bd5dc8","type":"link in","z":"232d7ecc.e5ff62","name":"StopCountdown","links":["3465588b.53b6a8"],"x":215,"y":1640,"wires":[["fe734a7b.67d5f8"]]},{"id":"3465588b.53b6a8","type":"link out","z":"232d7ecc.e5ff62","name":"StopCountdown","links":["8d623664.bd5dc8"],"x":815,"y":1080,"wires":[]},{"id":"b97af66.15a2508","type":"inject","z":"232d7ecc.e5ff62","name":"Once only","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":"","topic":"","payload":"off","payloadType":"str","x":430,"y":1840,"wires":[["c2c42385.eacfe"]]},{"id":"c5e54bf4.696278","type":"function","z":"232d7ecc.e5ff62","name":"sync followed by","func":"if (typeof (flow.get(\"countdown_followed_by\")) == 'undefined') \n{\n    flow.set(\"countdown_followed_by\",\"off\");\n}\n\nmsg.payload = flow.get(\"countdown_followed_by\");\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":840,"y":1140,"wires":[["1852bec.bed1941"]]},{"id":"e9cd724f.96163","type":"function","z":"232d7ecc.e5ff62","name":"store followed by","func":"switch (msg.payload){\ncase 'off':\n    flow.set(\"countdown_followed_by\",\"off\")\n    return msg;\n\ncase 'schedule':\n    flow.set(\"countdown_followed_by\",\"schedule\")\n    return msg;\n}\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1230,"y":1140,"wires":[["7fecf6a8.09c568"]]},{"id":"7fecf6a8.09c568","type":"debug","z":"232d7ecc.e5ff62","name":"followed_by_debug","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1450,"y":1140,"wires":[]},{"id":"8085802d.c81a1","type":"function","z":"232d7ecc.e5ff62","name":"Update setting button","func":"var button_link = {};\nbutton_link.group = {};\n\nvar msg2 = {};\n\nswitch (msg.payload){\ncase 'off':\n    msg.ui_control = {\"icon\": \"\"};\n    msg.label = \"\";\n    msg.enabled = false;\n    button_link.group.show = [];\n    button_link.group.hide = [];\n    msg2.payload = \"\";\n    break;\ncase 'schedule':\n    msg.ui_control = {\"icon\": \"fa-calendar\"};\n    msg.label = \"Set Schedule\";\n    msg.enabled = true;\n    button_link.group.show = [\"Cronotermostato_Programmazione\"];\n    button_link.group.hide = [\"Cronotermostato_Nest\"];\n    msg2.payload = \"\";\n    break;\ncase 'countdown':\n    msg.ui_control = {\"icon\": \"timer\"};\n    msg.label = \"Set Countdown\";\n    msg.enabled = true;    \n    button_link.group.show = [\"Cronotermostato_Countdown\"];\n    button_link.group.hide = [\"Cronotermostato_Nest\"];\n    msg2 = null;\n    break;\ncase 'manual':\n    msg.ui_control = {\"icon\": \"\"};\n    msg.label = \"\";\n    msg.enabled = false;\n    button_link.group.show = [];\n    button_link.group.hide = [];\n    msg2.payload = \"(Remember to turn off the thermostat)\";\n    break;\n}\n\nflow.set(\"setting_link\", button_link);\nreturn [msg, msg2];","outputs":2,"noerr":0,"initialize":"","finalize":"","x":340,"y":1220,"wires":[["e5f159e4.88e208"],["49c6197.5b0eee8","1c644b05.149ac5"]],"outputLabels":["off",""]},{"id":"c218687b.20aa58","type":"ui_toast","z":"232d7ecc.e5ff62","position":"top left","displayTime":"1","highlight":"#e83a2c","sendall":true,"outputs":0,"ok":"OK","cancel":"","raw":false,"topic":"","name":"","x":860,"y":1200,"wires":[]},{"id":"3fdfe0fb.4473c","type":"link out","z":"232d7ecc.e5ff62","name":"RefreshNest","links":["fc43585a.d453c8"],"x":1215,"y":1920,"wires":[]},{"id":"12f419df.54eb46","type":"link in","z":"232d7ecc.e5ff62","name":"set_infotext","links":["ef938785.813cd8","4dabe362.4a5b7c","49c6197.5b0eee8"],"x":775,"y":1600,"wires":[["11942362.43008d"]]},{"id":"ef938785.813cd8","type":"link out","z":"232d7ecc.e5ff62","name":"set_infotext","links":["12f419df.54eb46"],"x":1215,"y":1960,"wires":[]},{"id":"4dabe362.4a5b7c","type":"link out","z":"232d7ecc.e5ff62","name":"set_infotext","links":["12f419df.54eb46"],"x":1055,"y":420,"wires":[]},{"id":"d3d798e4.0b8278","type":"function","z":"232d7ecc.e5ff62","name":"Show next hour temperature","func":"if (flow.get(\"thermostat_mode\") != \"schedule\") {\n    return null;\n}\n\nlet timing=global.get(\"timing\");\nvar nexthour = new Date(msg.time);\n\nvar fixed_temperature = timing[(nexthour.getDay()*24)+nexthour.getHours()];\n\nmsg.payload = \"Next hour: \" + fixed_temperature + \"°\";\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":900,"y":420,"wires":[["4dabe362.4a5b7c"]]},{"id":"1c644b05.149ac5","type":"moment","z":"232d7ecc.e5ff62","name":"time + 1","topic":"","input":"","inputType":"date","inTz":"ETC/UTC","adjAmount":"1","adjType":"hours","adjDir":"add","format":"","locale":"en_EN","output":"time","outputType":"msg","outTz":"ETC/UTC","x":580,"y":420,"wires":[["d3d798e4.0b8278"]]},{"id":"49c6197.5b0eee8","type":"link out","z":"232d7ecc.e5ff62","name":"set_infotext","links":["12f419df.54eb46"],"x":515,"y":1240,"wires":[]},{"id":"d2cd7828.63a4c8","type":"comment","z":"232d7ecc.e5ff62","name":"set this","info":"","x":90,"y":720,"wires":[]},{"id":"2df535e5.4e138a","type":"comment","z":"232d7ecc.e5ff62","name":"set this","info":"","x":90,"y":780,"wires":[]},{"id":"3a2b2084.6de2b","type":"comment","z":"232d7ecc.e5ff62","name":"set this","info":"","x":1010,"y":960,"wires":[]},{"id":"73aa30d9.f56b7","type":"comment","z":"232d7ecc.e5ff62","name":"set this","info":"","x":1010,"y":1020,"wires":[]},{"id":"1d125180.4f249f","type":"comment","z":"232d7ecc.e5ff62","name":"set this","info":"","x":1290,"y":560,"wires":[]},{"id":"d6fc34ae.c4de78","type":"ui_ui_control","z":"232d7ecc.e5ff62","name":"ui_control on connect","events":"connect","x":320,"y":1300,"wires":[["41623664.5ae628"]]},{"id":"41623664.5ae628","type":"function","z":"232d7ecc.e5ff62","name":"hide groups","func":"msg.payload = {\"group\":{\"hide\":[\"Cronotermostato_Programmazione\",\"Cronotermostato_Grafico\",\"Cronotermostato_Countdown\"],\"show\":[\"Cronotermostato_Nest\"]}}\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":550,"y":1300,"wires":[["831777b7.320428"]]},{"id":"9599192.8ae45e8","type":"comment","z":"232d7ecc.e5ff62","name":"set your Timezone in these three nodes","info":"","x":570,"y":380,"wires":[]},{"id":"77b849cb.094c08","type":"ui_group","name":"Programmazione","tab":"5d954e3e.9339b","order":2,"disp":false,"width":8,"collapse":false},{"id":"317e9d9c.dea8e2","type":"ui_group","name":"Nest","tab":"5d954e3e.9339b","order":1,"disp":false,"width":8,"collapse":false},{"id":"41108723.0bf848","type":"server","name":"Home Assistant","version":2,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true,"heartbeat":false,"heartbeatInterval":30},{"id":"eaea6ba8.f76288","type":"ui_group","name":"Grafico","tab":"5d954e3e.9339b","order":3,"disp":false,"width":8,"collapse":false},{"id":"e767a130.9c455","type":"ui_group","name":"Countdown","tab":"5d954e3e.9339b","order":4,"disp":false,"width":"8","collapse":false},{"id":"5d954e3e.9339b","type":"ui_tab","name":"Cronotermostato","icon":"dashboard","order":1,"disabled":false,"hidden":false}]

Flow Info

Created 4 years, 10 months ago
Updated 2 years, 4 months ago
Rating: 3 2

Owner

Actions

Rate:

Node Types

Core
  • change (x2)
  • comment (x6)
  • debug (x3)
  • file (x2)
  • file in (x2)
  • function (x22)
  • inject (x5)
  • json (x2)
  • link in (x6)
  • link out (x9)
  • switch (x3)
Other
  • api-call-service (x3)
  • moment (x3)
  • mytimeout (x1)
  • server (x1)
  • server-state-changed (x2)
  • subflow (x1)
  • subflow:8850e193.e1712 (x1)
  • tab (x1)
  • ui_button (x6)
  • ui_chart (x1)
  • ui_dropdown (x2)
  • ui_gauge (x1)
  • ui_group (x4)
  • ui_slider (x1)
  • ui_switch (x1)
  • ui_tab (x1)
  • ui_template (x3)
  • ui_text (x3)
  • ui_toast (x2)
  • ui_ui_control (x2)

Tags

  • NEST
  • thermostat
  • hass.io
  • scheduler
  • dashboard
  • temperature
  • climate
  • week
  • ui
  • homeassistant
Copy this flow JSON to your clipboard and then import into Node-RED using the Import From > Clipboard (Ctrl-I) menu option