Skip to content

Instantly share code, notes, and snippets.

@giuseppeg88
Last active April 15, 2024 11:22
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save giuseppeg88/25857cba1ab7e34309133ac8354004c6 to your computer and use it in GitHub Desktop.
Save giuseppeg88/25857cba1ab7e34309133ac8354004c6 to your computer and use it in GitHub Desktop.
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
}
]
[
{
"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
}
]
[
{
"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": "c7e7982a.89e5f8",
"type": "tab",
"label": "The Ultimate Thermostat v3.4 ITA - 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": "b5b4d03.75d053",
"type": "ui_template",
"z": "c7e7982a.89e5f8",
"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": 260,
"y": 80,
"wires": [
[]
]
},
{
"id": "a0aab4d8.8b6498",
"type": "ui_template",
"z": "c7e7982a.89e5f8",
"group": "77b849cb.094c08",
"name": "Settings page",
"order": 1,
"width": 8,
"height": 5,
"format": "<script>\nvar thedays=[\"DOMENICA\",\"LUNEDI\",\"MARTEDI\",\"MERCOLEDI\",\"GIOVEDI\",\"VENERDI\",\"SABATO\"];\nvar ID = \"AA\";\n\nvar last=29;\n\nfunction bar(mm,val)\n{\nif (val>=12 && val<13) { $(mm).height(\"12%\"); $(mm).css('background-color', '#1E88E5'); } //blue\nif (val>=13 && val<14) { $(mm).height(\"16%\"); $(mm).css('background-color', '#039BE5'); }\nif (val>=14 && val<15) { $(mm).height(\"20%\"); $(mm).css('background-color', '#00ACC1'); } \nif (val>=15 && val<16) { $(mm).height(\"24%\"); $(mm).css('background-color', '#039BE5'); } // cyan\nif (val>=16 && val<17) { $(mm).height(\"28%\"); $(mm).css('background-color', '#00ACC1'); }\nif (val>=17 && val<18) { $(mm).height(\"32%\"); $(mm).css('background-color', '#00897B'); } \nif (val>=18 && val<19) { $(mm).height(\"36%\"); $(mm).css('background-color', '#388E3C'); } // green\nif (val>=19 && val<20) { $(mm).height(\"40%\"); $(mm).css('background-color', '#689F38'); }\nif (val>=20 && val<21) { $(mm).height(\"44%\"); $(mm).css('background-color', '#C0CA33'); }\nif (val>=21 && val<22) { $(mm).height(\"48%\"); $(mm).css('background-color', '#FDD835'); } // yellow\nif (val>=22 && val<23) { $(mm).height(\"52%\"); $(mm).css('background-color', '#FBC02D'); }\nif (val>=23 && val<24) { $(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\" >Giorno</span></center></td>\n <!--<td colspan=3><center><span class=\"smallheadings\">Clima</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> Azioni: </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\">DECREMENTA</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\">INCREMENTA</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\">COPIA</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\">SALVA</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\">ANNULLA</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": 290,
"y": 140,
"wires": [
[
"9773ea79.6d18f8"
]
]
},
{
"id": "9773ea79.6d18f8",
"type": "function",
"z": "c7e7982a.89e5f8",
"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=\"Limite raggiunto\";}\n else msg.foryou=\"Temperatura aumentata\";\n }\n if (context.selector==1) { if (timing[168+(context.selector-1)]<24) { timing[168+(context.selector-1)]+=0.5; msg.foryou=\"Temperatura clima aumentata di 0.5°C\"; } else msg.foryou=\"Limite raggiunto\"; }\n if (context.selector==2) { if (timing[168+(context.selector-1)]<24) { timing[168+(context.selector-1)]+=0.5; msg.foryou=\"Temperatura fuori casa aumentata di 0.5°C\"; } else msg.foryou=\"Limite raggiunto\"; }\n if (context.selector==29) { msg.foryou=\"Giorno successivo\"; 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=\"Limite raggiunto\";}\n else msg.foryou=\"Temperatura decrementata\";\n }\n if (context.selector==1) { if (timing[168+(context.selector-1)]>12) { timing[168+(context.selector-1)]-=0.5; msg.foryou=\"Temperatura clima diminuita di 0.5°C\"; } else msg.foryou=\"Limite raggiunto\"; }\n if (context.selector==2) { if (timing[168+(context.selector-1)]>12) { timing[168+(context.selector-1)]-=0.5; msg.foryou=\"Temperatura fuori casa diminuita di 0.5°C\"; } else msg.foryou=\"Limite raggiunto\"; }\n if (context.selector==29) { msg.foryou=\"Giorno precedente\"; 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=\"Temperatura copiata nello slot successivo\";\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=\"Impostazioni copiate nel giorno successivo\";\n }\n else if(context.days==7)\n msg.foryou=\"Fine della settimana raggiunto!\";\n break;\n case 's': context.saving=0; msg.foryou=\"Impostazioni salvate\"; 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=\"Temperatura clima selezionata\"; \n else if (msg.payload=='2') msg.foryou=\"Temperatura fuori casa selezionata\";\n else if (msg.payload=='29') msg.foryou=\"Giorno selezionato\";\n else msg.foryou=\"Regolazione temperatura ore: \" + (parseInt(msg.payload)-5);\n break;\n case 'c' : msg.payload=\"niente\"; msg.foryou=\"Cambiamenti annullati\"; 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": 560,
"y": 200,
"wires": [
[
"e0fd4ef8.cf0b3"
],
[
"780ced4a.7679d4"
],
[
"20ded924.9b9306"
]
]
},
{
"id": "c128a3ee.22a46",
"type": "inject",
"z": "c7e7982a.89e5f8",
"name": "Once only",
"props": [
{
"p": "payload",
"v": "",
"vt": "str"
},
{
"p": "topic",
"v": "",
"vt": "string"
}
],
"repeat": "",
"crontab": "",
"once": true,
"topic": "",
"payload": "",
"payloadType": "str",
"x": 298,
"y": 237,
"wires": [
[
"9773ea79.6d18f8",
"a0408ce0.f23a8"
]
]
},
{
"id": "74fef445.d62e9c",
"type": "inject",
"z": "c7e7982a.89e5f8",
"name": "Every minute",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "60",
"crontab": "",
"once": true,
"onceDelay": "",
"topic": "",
"payload": "",
"payloadType": "str",
"x": 320,
"y": 480,
"wires": [
[
"7ba12336.80a24c",
"266cc8e.7387c38"
]
]
},
{
"id": "a805343b.fe6d88",
"type": "function",
"z": "c7e7982a.89e5f8",
"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": 890,
"y": 480,
"wires": [
[
"f0e9bddf.11ad3"
]
]
},
{
"id": "780ced4a.7679d4",
"type": "file",
"z": "c7e7982a.89e5f8",
"name": "backup",
"filename": "/share/thermo_scheduler.save",
"appendNewline": true,
"createDir": true,
"overwriteFile": "true",
"x": 750,
"y": 200,
"wires": [
[]
]
},
{
"id": "a0408ce0.f23a8",
"type": "file in",
"z": "c7e7982a.89e5f8",
"name": "Restore",
"filename": "/share/thermo_scheduler.save",
"format": "utf8",
"sendError": true,
"x": 530,
"y": 280,
"wires": [
[
"5cdbcb09.d2b184"
]
]
},
{
"id": "5cdbcb09.d2b184",
"type": "function",
"z": "c7e7982a.89e5f8",
"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": 750,
"y": 280,
"wires": [
[
"8cc70579.6ac458"
]
]
},
{
"id": "645bd732.1d8128",
"type": "moment",
"z": "c7e7982a.89e5f8",
"name": "",
"topic": "",
"input": "payload",
"inputType": "msg",
"inTz": "Europe/Rome",
"adjAmount": "0",
"adjType": "hours",
"adjDir": "add",
"format": "dddd DD MMMM YYYY - HH:mm",
"locale": "it_IT",
"output": "payload",
"outputType": "msg",
"outTz": "Europe/Rome",
"x": 620,
"y": 560,
"wires": [
[
"5a3f0ac6.23d634"
]
]
},
{
"id": "5a3f0ac6.23d634",
"type": "ui_text",
"z": "c7e7982a.89e5f8",
"group": "317e9d9c.dea8e2",
"order": 2,
"width": 6,
"height": 1,
"name": "Time",
"label": "",
"format": "{{msg.payload}}",
"layout": "col-center",
"x": 870,
"y": 560,
"wires": []
},
{
"id": "eb3fb3aa.cba34",
"type": "link in",
"z": "c7e7982a.89e5f8",
"name": "restore",
"links": [
"20ded924.9b9306"
],
"x": 351.5,
"y": 288,
"wires": [
[
"a0408ce0.f23a8"
]
]
},
{
"id": "20ded924.9b9306",
"type": "link out",
"z": "c7e7982a.89e5f8",
"name": "restore",
"links": [
"eb3fb3aa.cba34",
"93466115.e953e"
],
"x": 711.5,
"y": 236,
"wires": []
},
{
"id": "106201b7.f5bb7e",
"type": "link in",
"z": "c7e7982a.89e5f8",
"name": "restoreSD",
"links": [
"8cc70579.6ac458",
"8e1afd82.fc23f",
"aa45f621.310328"
],
"x": 351,
"y": 189,
"wires": [
[
"9773ea79.6d18f8"
]
]
},
{
"id": "8cc70579.6ac458",
"type": "link out",
"z": "c7e7982a.89e5f8",
"name": "restoreSD",
"links": [
"106201b7.f5bb7e"
],
"x": 945,
"y": 280,
"wires": []
},
{
"id": "e0fd4ef8.cf0b3",
"type": "link out",
"z": "c7e7982a.89e5f8",
"name": "setSettings",
"links": [
"1a23cdc.2ee0b32"
],
"x": 712.5,
"y": 160,
"wires": []
},
{
"id": "1a23cdc.2ee0b32",
"type": "link in",
"z": "c7e7982a.89e5f8",
"name": "setSettings",
"links": [
"e0fd4ef8.cf0b3"
],
"x": 157.5,
"y": 137,
"wires": [
[
"a0aab4d8.8b6498"
]
]
},
{
"id": "7ba12336.80a24c",
"type": "moment",
"z": "c7e7982a.89e5f8",
"name": "time",
"topic": "",
"input": "",
"inputType": "date",
"inTz": "Europe/Rome",
"adjAmount": "0",
"adjType": "hours",
"adjDir": "add",
"format": "",
"locale": "it_IT",
"output": "time",
"outputType": "msg",
"outTz": "Europe/Rome",
"x": 610,
"y": 480,
"wires": [
[
"a805343b.fe6d88"
]
]
},
{
"id": "64c9da3e.3127b4",
"type": "function",
"z": "c7e7982a.89e5f8",
"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": 510,
"y": 780,
"wires": [
[
"f8f19df4.f2fd7"
]
]
},
{
"id": "f0e9bddf.11ad3",
"type": "api-call-service",
"z": "c7e7982a.89e5f8",
"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": 1160,
"y": 560,
"wires": [
[]
]
},
{
"id": "a2f11a6d.ff7e28",
"type": "api-call-service",
"z": "c7e7982a.89e5f8",
"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": 900,
"y": 1020,
"wires": [
[]
]
},
{
"id": "180c3495.1167cb",
"type": "api-call-service",
"z": "c7e7982a.89e5f8",
"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": 900,
"y": 960,
"wires": [
[]
]
},
{
"id": "c5a858fe.e4ae98",
"type": "server-state-changed",
"z": "c7e7982a.89e5f8",
"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": 260,
"y": 780,
"wires": [
[
"64c9da3e.3127b4"
]
]
},
{
"id": "95403a92.332ff8",
"type": "inject",
"z": "c7e7982a.89e5f8",
"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": 320,
"y": 560,
"wires": [
[
"645bd732.1d8128"
]
]
},
{
"id": "97c4518e.c0731",
"type": "ui_template",
"z": "c7e7982a.89e5f8",
"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": 630,
"y": 640,
"wires": [
[
"8f7013c0.fc602"
]
]
},
{
"id": "f8f19df4.f2fd7",
"type": "function",
"z": "c7e7982a.89e5f8",
"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": 750,
"y": 760,
"wires": [
[
"97c4518e.c0731",
"f774f7fb.980448"
],
[
"e80beafb.960048",
"f21ea4a8.3d7e28"
],
[
"8d3e7ccb.05e8a"
]
]
},
{
"id": "8f7013c0.fc602",
"type": "function",
"z": "c7e7982a.89e5f8",
"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": 860,
"y": 640,
"wires": [
[
"f0e9bddf.11ad3"
]
]
},
{
"id": "85c35de5.000d",
"type": "server-state-changed",
"z": "c7e7982a.89e5f8",
"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": 230,
"y": 720,
"wires": [
[
"d81ec755.7a8988",
"8190067a.0d5ff8"
]
]
},
{
"id": "f774f7fb.980448",
"type": "debug",
"z": "c7e7982a.89e5f8",
"name": "climate_debug",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"x": 940,
"y": 740,
"wires": []
},
{
"id": "d81ec755.7a8988",
"type": "function",
"z": "c7e7982a.89e5f8",
"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": 510,
"y": 720,
"wires": [
[
"f8f19df4.f2fd7"
]
]
},
{
"id": "30695c3e.d68cc4",
"type": "ui_switch",
"z": "c7e7982a.89e5f8",
"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": 430,
"y": 1000,
"wires": [
[
"8d3e7ccb.05e8a"
]
]
},
{
"id": "8d3e7ccb.05e8a",
"type": "switch",
"z": "c7e7982a.89e5f8",
"name": "",
"property": "payload",
"propertyType": "msg",
"rules": [
{
"t": "false"
},
{
"t": "true"
}
],
"checkall": "true",
"repair": false,
"outputs": 2,
"x": 590,
"y": 1000,
"wires": [
[
"180c3495.1167cb",
"cef71c8c.a9bc5"
],
[
"a2f11a6d.ff7e28"
]
]
},
{
"id": "8190067a.0d5ff8",
"type": "function",
"z": "c7e7982a.89e5f8",
"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": 210,
"y": 960,
"wires": [
[
"30695c3e.d68cc4"
],
[
"f21ea4a8.3d7e28"
]
]
},
{
"id": "4803668a.123128",
"type": "ui_button",
"z": "c7e7982a.89e5f8",
"name": "",
"group": "77b849cb.094c08",
"order": 2,
"width": 8,
"height": 1,
"passthru": false,
"label": "Torna alla home",
"tooltip": "",
"color": "{{colour}}",
"bgcolor": "{{payload}}",
"icon": "",
"payload": "{\"group\":{\"hide\":[\"Cronotermostato_Programmazione\"],\"show\":[\"Cronotermostato_Nest\"]}}",
"payloadType": "json",
"topic": "",
"x": 640,
"y": 1340,
"wires": [
[
"fa178067.0ef11",
"df7585f3.52b3d8"
]
]
},
{
"id": "fa178067.0ef11",
"type": "ui_ui_control",
"z": "c7e7982a.89e5f8",
"name": "",
"events": "all",
"x": 900,
"y": 1360,
"wires": [
[]
]
},
{
"id": "8a7e776.5e09688",
"type": "ui_chart",
"z": "c7e7982a.89e5f8",
"name": "",
"group": "eaea6ba8.f76288",
"order": 1,
"width": 8,
"height": 5,
"label": "Grafico cronologico",
"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": 690,
"y": 2100,
"wires": [
[
"b8478180.cc3d2",
"8fa4fc95.6ccb"
]
]
},
{
"id": "d9362c57.332e",
"type": "function",
"z": "c7e7982a.89e5f8",
"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": 450,
"y": 2100,
"wires": [
[
"8a7e776.5e09688"
],
[
"8a7e776.5e09688"
]
]
},
{
"id": "3c197bdb.d59684",
"type": "ui_button",
"z": "c7e7982a.89e5f8",
"name": "",
"group": "eaea6ba8.f76288",
"order": 2,
"width": 8,
"height": 1,
"passthru": false,
"label": "Torna alla home",
"tooltip": "",
"color": "{{colour}}",
"bgcolor": "{{payload}}",
"icon": "",
"payload": "{\"group\":{\"hide\":[\"Cronotermostato_Grafico\"],\"show\":[\"Cronotermostato_Nest\"]}}",
"payloadType": "json",
"topic": "",
"x": 640,
"y": 1440,
"wires": [
[
"fa178067.0ef11",
"df7585f3.52b3d8"
]
]
},
{
"id": "4faba555.b954dc",
"type": "ui_button",
"z": "c7e7982a.89e5f8",
"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": 610,
"y": 1380,
"wires": [
[
"fa178067.0ef11",
"7d9d27e5.6bd938"
]
]
},
{
"id": "37ae5a89.01cd36",
"type": "inject",
"z": "c7e7982a.89e5f8",
"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": 240,
"y": 2100,
"wires": [
[
"d9362c57.332e",
"7af169ef.9510a8"
]
]
},
{
"id": "b8478180.cc3d2",
"type": "subflow:8850e193.e1712",
"z": "c7e7982a.89e5f8",
"name": "",
"env": [],
"x": 870,
"y": 2100,
"wires": [
[
"b7aa55dc.b434c8"
]
]
},
{
"id": "7d9d27e5.6bd938",
"type": "function",
"z": "c7e7982a.89e5f8",
"name": "restore chart",
"func": "msg.topic=\"restore\"\nreturn msg;",
"outputs": 1,
"noerr": 0,
"x": 850,
"y": 1520,
"wires": [
[]
]
},
{
"id": "8fa4fc95.6ccb",
"type": "debug",
"z": "c7e7982a.89e5f8",
"name": "grafico_debug",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"x": 880,
"y": 2040,
"wires": []
},
{
"id": "634ced23.8751e4",
"type": "function",
"z": "c7e7982a.89e5f8",
"name": "show today",
"func": "flow.set(\"resetday\",true);\n\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"x": 870,
"y": 1280,
"wires": [
[
"fa178067.0ef11"
]
]
},
{
"id": "3ad37bad.3b6564",
"type": "change",
"z": "c7e7982a.89e5f8",
"name": "",
"rules": [
{
"t": "set",
"p": "yves",
"pt": "global",
"to": "payload[0]",
"tot": "msg"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 1220,
"y": 2100,
"wires": [
[]
]
},
{
"id": "b7aa55dc.b434c8",
"type": "switch",
"z": "c7e7982a.89e5f8",
"name": "",
"property": "payload",
"propertyType": "msg",
"rules": [
{
"t": "istype",
"v": "undefined",
"vt": "undefined"
},
{
"t": "else"
}
],
"checkall": "true",
"repair": false,
"outputs": 2,
"x": 1030,
"y": 2100,
"wires": [
[],
[
"3ad37bad.3b6564"
]
]
},
{
"id": "7af169ef.9510a8",
"type": "change",
"z": "c7e7982a.89e5f8",
"name": "global.yves",
"rules": [
{
"t": "set",
"p": "payload",
"pt": "msg",
"to": "yves",
"tot": "global"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 450,
"y": 2160,
"wires": [
[
"8a7e776.5e09688"
]
]
},
{
"id": "5021393b.a4a638",
"type": "ui_button",
"z": "c7e7982a.89e5f8",
"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": 370,
"y": 1680,
"wires": [
[
"e3e8f114.0795b"
]
]
},
{
"id": "e356a4a.bc2a558",
"type": "function",
"z": "c7e7982a.89e5f8",
"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 = \"Secondi rimanenti: \" + (minutes*60).toFixed(0);\n }\n else\n {\n msg3.payload = \"Minuti rimanenti: \" + minutes.toFixed(0);\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": 1020,
"y": 1920,
"wires": [
[
"bb3cc3ca.9642a"
],
[
"3a1c4056.fd71a"
],
[
"66c9691b.d63478"
]
]
},
{
"id": "bb3cc3ca.9642a",
"type": "ui_gauge",
"z": "c7e7982a.89e5f8",
"name": "Countdown Timer",
"group": "e767a130.9c455",
"order": 1,
"width": 6,
"height": 4,
"gtype": "donut",
"title": "",
"label": "Minuti",
"format": "{{value | number:0}}",
"min": 0,
"max": "60",
"colors": [
"#ff2600",
"#e6e600",
"#00f900"
],
"seg1": "",
"seg2": "",
"x": 1230,
"y": 1800,
"wires": []
},
{
"id": "a6f16c.5d5d8e98",
"type": "function",
"z": "c7e7982a.89e5f8",
"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": 450,
"y": 1880,
"wires": [
[
"b693b186.25f61"
]
]
},
{
"id": "601e97b7.a9d318",
"type": "ui_slider",
"z": "c7e7982a.89e5f8",
"name": "",
"label": "Minuti",
"tooltip": "Timer avviato al rilascio",
"group": "e767a130.9c455",
"order": 6,
"width": 6,
"height": 1,
"passthru": true,
"outs": "end",
"topic": "",
"min": "0",
"max": "120",
"step": 1,
"x": 230,
"y": 1840,
"wires": [
[
"a6f16c.5d5d8e98",
"dc8ecafd.2056b8"
]
]
},
{
"id": "146358ea.a1b987",
"type": "ui_toast",
"z": "c7e7982a.89e5f8",
"position": "top left",
"displayTime": "1",
"highlight": "#e83a2c",
"sendall": true,
"outputs": 0,
"ok": "OK",
"cancel": "",
"raw": false,
"topic": "",
"name": "",
"x": 960,
"y": 1840,
"wires": []
},
{
"id": "b693b186.25f61",
"type": "mytimeout",
"z": "c7e7982a.89e5f8",
"name": "Timer 1-120 Minutes",
"outtopic": "",
"outsafe": "",
"outwarning": "Il Countdown è quasi finito",
"outunsafe": "Countdown terminato",
"warning": "",
"timer": "",
"debug": false,
"ndebug": true,
"ignoreCase": false,
"repeat": false,
"again": false,
"x": 720,
"y": 1880,
"wires": [
[
"146358ea.a1b987"
],
[
"e356a4a.bc2a558"
]
]
},
{
"id": "dc8ecafd.2056b8",
"type": "function",
"z": "c7e7982a.89e5f8",
"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": 430,
"y": 1800,
"wires": [
[
"bb3cc3ca.9642a"
]
]
},
{
"id": "e3e8f114.0795b",
"type": "function",
"z": "c7e7982a.89e5f8",
"name": "Reset Slider",
"func": "msg.payload = 0;\n\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"x": 570,
"y": 1680,
"wires": [
[
"601e97b7.a9d318"
]
]
},
{
"id": "5b0d0cf.c059ef4",
"type": "ui_button",
"z": "c7e7982a.89e5f8",
"name": "",
"group": "e767a130.9c455",
"order": 8,
"width": 8,
"height": 1,
"passthru": false,
"label": "Torna alla home",
"tooltip": "",
"color": "{{colour}}",
"bgcolor": "{{payload}}",
"icon": "",
"payload": "{\"group\":{\"hide\":[\"Cronotermostato_Countdown\"],\"show\":[\"Cronotermostato_Nest\"]}}",
"payloadType": "json",
"topic": "",
"x": 640,
"y": 1500,
"wires": [
[
"fa178067.0ef11",
"df7585f3.52b3d8"
]
]
},
{
"id": "f21ea4a8.3d7e28",
"type": "ui_dropdown",
"z": "c7e7982a.89e5f8",
"name": "",
"label": "",
"tooltip": "",
"place": "Select mode",
"group": "317e9d9c.dea8e2",
"order": 5,
"width": 3,
"height": 1,
"passthru": false,
"multiple": false,
"options": [
{
"label": "Spento",
"value": "off",
"type": "str"
},
{
"label": "Programma",
"value": "schedule",
"type": "str"
},
{
"label": "Countdown",
"value": "countdown",
"type": "str"
},
{
"label": "Manuale",
"value": "manual",
"type": "str"
}
],
"payload": "",
"topic": "",
"x": 280,
"y": 1100,
"wires": [
[
"52b5f34a.e5438c",
"e80beafb.960048"
]
]
},
{
"id": "52b5f34a.e5438c",
"type": "function",
"z": "c7e7982a.89e5f8",
"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": 500,
"y": 1100,
"wires": [
[
"180c3495.1167cb",
"cef71c8c.a9bc5"
],
[
"634ced23.8751e4",
"a2f11a6d.ff7e28",
"cef71c8c.a9bc5"
],
[
"fa178067.0ef11",
"2111533e.56fbec"
],
[
"a2f11a6d.ff7e28",
"cef71c8c.a9bc5",
"f90c64e4.b6bd58"
]
],
"outputLabels": [
"off",
"schedule",
"countdown",
"manual"
]
},
{
"id": "76d7a3ad.82706c",
"type": "ui_text",
"z": "c7e7982a.89e5f8",
"group": "317e9d9c.dea8e2",
"order": 7,
"width": 3,
"height": 3,
"name": "",
"label": "",
"format": "{{msg.payload}}",
"layout": "row-left",
"x": 910,
"y": 1600,
"wires": []
},
{
"id": "548f9ce2.872cb4",
"type": "ui_button",
"z": "c7e7982a.89e5f8",
"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": 590,
"y": 1200,
"wires": [
[
"fa178067.0ef11",
"634ced23.8751e4"
]
]
},
{
"id": "46acbaca.a27cb4",
"type": "ui_dropdown",
"z": "c7e7982a.89e5f8",
"name": "",
"label": "",
"tooltip": "",
"place": "After",
"group": "e767a130.9c455",
"order": 4,
"width": 2,
"height": 1,
"passthru": false,
"multiple": false,
"options": [
{
"label": "Spento",
"value": "off",
"type": "str"
},
{
"label": "Programma",
"value": "schedule",
"type": "str"
}
],
"payload": "",
"topic": "",
"x": 1060,
"y": 1140,
"wires": [
[
"f6e4894d.68e478"
]
]
},
{
"id": "604038e5.65ef58",
"type": "ui_text",
"z": "c7e7982a.89e5f8",
"group": "e767a130.9c455",
"order": 3,
"width": 2,
"height": 1,
"name": "",
"label": "Seguito dalla modalità:",
"format": "{{msg.payload}}",
"layout": "col-center",
"x": 1100,
"y": 1100,
"wires": []
},
{
"id": "df7585f3.52b3d8",
"type": "link out",
"z": "c7e7982a.89e5f8",
"name": "RefreshNest",
"links": [
"582b7fce.e50f6"
],
"x": 875,
"y": 1440,
"wires": []
},
{
"id": "582b7fce.e50f6",
"type": "link in",
"z": "c7e7982a.89e5f8",
"name": "RefreshNest",
"links": [
"df7585f3.52b3d8",
"3a1c4056.fd71a"
],
"x": 655,
"y": 820,
"wires": [
[
"f8f19df4.f2fd7"
]
]
},
{
"id": "d0e07eaf.acadd",
"type": "link in",
"z": "c7e7982a.89e5f8",
"name": "StopCountdown",
"links": [
"cef71c8c.a9bc5"
],
"x": 255,
"y": 1640,
"wires": [
[
"5021393b.a4a638"
]
]
},
{
"id": "cef71c8c.a9bc5",
"type": "link out",
"z": "c7e7982a.89e5f8",
"name": "StopCountdown",
"links": [
"d0e07eaf.acadd"
],
"x": 855,
"y": 1080,
"wires": []
},
{
"id": "715a16df.299d68",
"type": "inject",
"z": "c7e7982a.89e5f8",
"name": "Once only",
"props": [
{
"p": "payload"
}
],
"repeat": "",
"crontab": "",
"once": true,
"onceDelay": "",
"topic": "",
"payload": "off",
"payloadType": "str",
"x": 470,
"y": 1840,
"wires": [
[
"b693b186.25f61"
]
]
},
{
"id": "2111533e.56fbec",
"type": "function",
"z": "c7e7982a.89e5f8",
"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": 880,
"y": 1140,
"wires": [
[
"46acbaca.a27cb4"
]
]
},
{
"id": "f6e4894d.68e478",
"type": "function",
"z": "c7e7982a.89e5f8",
"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": 1270,
"y": 1140,
"wires": [
[
"63502f69.e7ed8"
]
]
},
{
"id": "63502f69.e7ed8",
"type": "debug",
"z": "c7e7982a.89e5f8",
"name": "followed_by_debug",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 1490,
"y": 1140,
"wires": []
},
{
"id": "e80beafb.960048",
"type": "function",
"z": "c7e7982a.89e5f8",
"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 = \"Imposta programma\";\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 = \"Imposta 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 = \"(Ricordarsi di spegnere il termostato)\";\n break;\n}\n\nflow.set(\"setting_link\", button_link);\nreturn [msg, msg2];",
"outputs": 2,
"noerr": 0,
"initialize": "",
"finalize": "",
"x": 380,
"y": 1220,
"wires": [
[
"548f9ce2.872cb4"
],
[
"265b809.020228",
"266cc8e.7387c38"
]
],
"outputLabels": [
"off",
""
]
},
{
"id": "f90c64e4.b6bd58",
"type": "ui_toast",
"z": "c7e7982a.89e5f8",
"position": "top left",
"displayTime": "1",
"highlight": "#e83a2c",
"sendall": true,
"outputs": 0,
"ok": "OK",
"cancel": "",
"raw": false,
"topic": "",
"name": "",
"x": 900,
"y": 1200,
"wires": []
},
{
"id": "3a1c4056.fd71a",
"type": "link out",
"z": "c7e7982a.89e5f8",
"name": "RefreshNest",
"links": [
"582b7fce.e50f6"
],
"x": 1255,
"y": 1920,
"wires": []
},
{
"id": "d14b2224.e583",
"type": "link in",
"z": "c7e7982a.89e5f8",
"name": "set_infotext",
"links": [
"66c9691b.d63478",
"40dd5def.7dbcb4",
"265b809.020228"
],
"x": 815,
"y": 1600,
"wires": [
[
"76d7a3ad.82706c"
]
]
},
{
"id": "66c9691b.d63478",
"type": "link out",
"z": "c7e7982a.89e5f8",
"name": "set_infotext",
"links": [
"d14b2224.e583"
],
"x": 1255,
"y": 1960,
"wires": []
},
{
"id": "40dd5def.7dbcb4",
"type": "link out",
"z": "c7e7982a.89e5f8",
"name": "set_infotext",
"links": [
"d14b2224.e583"
],
"x": 1095,
"y": 420,
"wires": []
},
{
"id": "4014445.26c60bc",
"type": "function",
"z": "c7e7982a.89e5f8",
"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 = \"Temperatura programmata per la prossima ora: \" + fixed_temperature + \"°\";\n\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 940,
"y": 420,
"wires": [
[
"40dd5def.7dbcb4"
]
]
},
{
"id": "266cc8e.7387c38",
"type": "moment",
"z": "c7e7982a.89e5f8",
"name": "time + 1",
"topic": "",
"input": "",
"inputType": "date",
"inTz": "Europe/Rome",
"adjAmount": "1",
"adjType": "hours",
"adjDir": "add",
"format": "",
"locale": "it_IT",
"output": "time",
"outputType": "msg",
"outTz": "Europe/Rome",
"x": 620,
"y": 420,
"wires": [
[
"4014445.26c60bc"
]
]
},
{
"id": "265b809.020228",
"type": "link out",
"z": "c7e7982a.89e5f8",
"name": "set_infotext",
"links": [
"d14b2224.e583"
],
"x": 555,
"y": 1240,
"wires": []
},
{
"id": "1583662b.eaa1ea",
"type": "comment",
"z": "c7e7982a.89e5f8",
"name": "set this",
"info": "",
"x": 130,
"y": 720,
"wires": []
},
{
"id": "90b52d0f.173c8",
"type": "comment",
"z": "c7e7982a.89e5f8",
"name": "set this",
"info": "",
"x": 130,
"y": 780,
"wires": []
},
{
"id": "f32ca0ec.42264",
"type": "comment",
"z": "c7e7982a.89e5f8",
"name": "set this",
"info": "",
"x": 1050,
"y": 960,
"wires": []
},
{
"id": "e2603d04.10a1e",
"type": "comment",
"z": "c7e7982a.89e5f8",
"name": "set this",
"info": "",
"x": 1050,
"y": 1020,
"wires": []
},
{
"id": "6882e6f4.346418",
"type": "comment",
"z": "c7e7982a.89e5f8",
"name": "set this",
"info": "",
"x": 1330,
"y": 560,
"wires": []
},
{
"id": "81a71617.0969f8",
"type": "ui_ui_control",
"z": "c7e7982a.89e5f8",
"name": "ui_control on connect",
"events": "connect",
"x": 400,
"y": 1280,
"wires": [
[
"d3918875.9486b8"
]
]
},
{
"id": "d3918875.9486b8",
"type": "function",
"z": "c7e7982a.89e5f8",
"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": 630,
"y": 1280,
"wires": [
[
"fa178067.0ef11"
]
]
},
{
"id": "af7851fa.ee551",
"type": "comment",
"z": "c7e7982a.89e5f8",
"name": "set your Timezone in these three nodes",
"info": "",
"x": 610,
"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
}
]
@rwGP-d3b6nx7Qv5JvqGB
Copy link

Thanks for sharing all this work! I hope you'll continue to share updates as I am having issues viewing it. Also, if you have any suggestions how a beginner might go about editing to get from celcius to farenheit, it'd be appreciated. I'll drop a clue only applicable to those trying to view the dashboard in hass.io, the root is in a bit of a different place: https://ipaddress:1880/endpoint/ui/

@keNny1979
Copy link

Hey. I need a function to start the heating at a specific time. So when I'm on my way home I can set the thermostat to start at a specified time. Actually I think I can only set the countdown to stop a specified time. Can you help me with this specific topic :D

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