Alex Corvis
Published © GPL3+

DIY Virtual Alike NEST Thermostat with Node-RED

Create your own DIY virtual alike NEST thermostat to set and control the temperature of your home wherever you are from your desired device.

AdvancedFull instructions provided8 hours12,936
DIY Virtual Alike NEST Thermostat with Node-RED

Things used in this project

Hardware components

Raspberry Pi 3 Model B
Raspberry Pi 3 Model B
×1
Itead 1 CHANNEL INCHING /SELF-LOCKING WIFI WIRELESS SWITCH 5V 12V
1 CHANNEL INCHING /SELF-LOCKING WIFI WIRELESS SWITCH 5V 12V
×1
SparkFun USB to Serial Breakout - FT232RL
SparkFun USB to Serial Breakout - FT232RL
×1
DHT22 Temperature Sensor
DHT22 Temperature Sensor
×1
Jumper wires (generic)
Jumper wires (generic)
×1

Software apps and online services

Raspberry Pi Raspbian
IBM Node-RED
Mosquitto
ESPurna custom firmware for ESP8266
PlatformIO IDE for Atom

Hand tools and fabrication machines

30ST Soldering iron

Story

Read more

Schematics

ITEAD 1CH WiFi Switch with DHT22 sensor

Img 20180214 154913 bdos8lexep

ITEAD 1CH WiFi Switch with Pins

Soldered Pins directly in order to let it be programmed via USB-to-Serial
Img 20180214 155247 7gxjov780l

ITEAD 1CH WiFi Switch connected to USB-to-Serial

ITEAD 1CH WiFi Switch connected to USB-to-Serial pins
Img 20180214 165555 90evbxwelk

ITEAD 1CH WiFi Switch being Flashed with ESPurna Firmware

ITEAD 1CH WiFi Switch being Flashed with ESPurna Firmware via USB-to-Serial with PlatformIO software
Img 20180214 170442 ggzihgbgnb

Code

DIY Virtual alike NEST Thermostat with Node-RED

JSON
Node-RED code to create your own DIY Virtual alike NEST Thermostat.
Copy, import in Node-RED, Deploy and enjoy your DIY Virtual NEST Thermostat :)
[
    {
        "id": "da904081.4e064",
        "type": "tab",
        "label": "Termostato",
        "disabled": false,
        "info": ""
    },
    {
        "id": "a41e02ae.5c398",
        "type": "mqtt-broker",
        "z": "",
        "broker": "localhost",
        "port": "1883",
        "clientid": "",
        "usetls": false,
        "compatmode": true,
        "keepalive": "60",
        "cleansession": true,
        "willTopic": "",
        "willQos": "0",
        "willPayload": "",
        "birthTopic": "",
        "birthQos": "0",
        "birthPayload": ""
    },
    {
        "id": "f9325f56.6c0ed",
        "type": "ui_group",
        "z": "",
        "name": "Medidas",
        "tab": "3c5450a1.d2046",
        "order": 3,
        "disp": true,
        "width": "6"
    },
    {
        "id": "3c5450a1.d2046",
        "type": "ui_tab",
        "z": "",
        "name": "Termostato IOT",
        "icon": "dashboard"
    },
    {
        "id": "40a14a18.fcded4",
        "type": "ui_base",
        "theme": {
            "name": "theme-dark",
            "lightTheme": {
                "default": "#0094CE",
                "baseColor": "#0094CE",
                "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif",
                "edited": true,
                "reset": false
            },
            "darkTheme": {
                "default": "#097479",
                "baseColor": "#097479",
                "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif",
                "edited": true,
                "reset": false
            },
            "customTheme": {
                "name": "Untitled Theme 1",
                "default": "#4B7930",
                "baseColor": "#4B7930",
                "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif"
            },
            "themeState": {
                "base-color": {
                    "default": "#097479",
                    "value": "#097479",
                    "edited": false
                },
                "page-titlebar-backgroundColor": {
                    "value": "#097479",
                    "edited": false
                },
                "page-backgroundColor": {
                    "value": "#111111",
                    "edited": false
                },
                "page-sidebar-backgroundColor": {
                    "value": "#000000",
                    "edited": false
                },
                "group-textColor": {
                    "value": "#0eb8c0",
                    "edited": false
                },
                "group-borderColor": {
                    "value": "#555555",
                    "edited": false
                },
                "group-backgroundColor": {
                    "value": "#333333",
                    "edited": false
                },
                "widget-textColor": {
                    "value": "#eeeeee",
                    "edited": false
                },
                "widget-backgroundColor": {
                    "value": "#097479",
                    "edited": false
                },
                "widget-borderColor": {
                    "value": "#333333",
                    "edited": false
                },
                "base-font": {
                    "value": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif"
                }
            }
        },
        "site": {
            "name": "Node-RED Dashboard",
            "hideToolbar": "false",
            "allowSwipe": "false",
            "dateFormat": "DD/MM/YYYY",
            "sizes": {
                "sx": 48,
                "sy": 48,
                "gx": 6,
                "gy": 6,
                "cx": 6,
                "cy": 6,
                "px": 0,
                "py": 0
            }
        }
    },
    {
        "id": "7c31da65.7796a4",
        "type": "ui_group",
        "z": "",
        "name": "Gráficas",
        "tab": "3c5450a1.d2046",
        "order": 2,
        "disp": true,
        "width": "6"
    },
    {
        "id": "95e5f821.53ff28",
        "type": "ui_group",
        "z": "",
        "name": "Control Tersmostato",
        "tab": "3c5450a1.d2046",
        "order": 1,
        "disp": true,
        "width": "6"
    },
    {
        "id": "4aae036e.54bd7c",
        "type": "mqtt-broker",
        "z": "",
        "broker": "localhost",
        "port": "1883",
        "clientid": "",
        "usetls": false,
        "compatmode": true,
        "keepalive": "60",
        "cleansession": true,
        "willTopic": "",
        "willQos": "0",
        "willPayload": "",
        "birthTopic": "",
        "birthQos": "0",
        "birthPayload": ""
    },
    {
        "id": "63d2ea9d.e92204",
        "type": "ui_group",
        "z": "",
        "name": "Lounge",
        "tab": "cbd9ace5.cfbaa",
        "order": 2,
        "disp": true,
        "width": "15"
    },
    {
        "id": "cdb819c.4aedce8",
        "type": "ui_group",
        "z": "",
        "name": "Stat",
        "tab": "ede1e3b6.feaad",
        "order": 1,
        "disp": false,
        "width": "6"
    },
    {
        "id": "654c185b.982bd8",
        "type": "ui_group",
        "z": "",
        "name": "Temperature",
        "tab": "43dfe241.1bc40c",
        "order": 1,
        "disp": false,
        "width": "8"
    },
    {
        "id": "7babdc3d.fd75e4",
        "type": "ui_group",
        "z": "",
        "name": "Test",
        "tab": "ffe3bf5.7c92d4",
        "disp": true,
        "width": "6"
    },
    {
        "id": "cbd9ace5.cfbaa",
        "type": "ui_tab",
        "z": "",
        "name": "Charts",
        "icon": "dashboard",
        "order": 4
    },
    {
        "id": "ede1e3b6.feaad",
        "type": "ui_tab",
        "z": "",
        "name": "Settings",
        "icon": "dashboard",
        "order": 2
    },
    {
        "id": "43dfe241.1bc40c",
        "type": "ui_tab",
        "z": "",
        "name": "Home",
        "icon": "dashboard",
        "order": 1
    },
    {
        "id": "ffe3bf5.7c92d4",
        "type": "ui_tab",
        "z": "",
        "name": "Test",
        "icon": "dashboard"
    },
    {
        "id": "69149863.7cc6c8",
        "type": "mqtt in",
        "z": "da904081.4e064",
        "name": "SENSOR AM2302-TEMP",
        "topic": "/home/Thermostat/temperature",
        "qos": "2",
        "broker": "a41e02ae.5c398",
        "x": 152.5,
        "y": 143.25000286102295,
        "wires": [
            [
                "81b5d072.aa892",
                "57f50795.dcd598",
                "26941838.3c1f98",
                "182fb20b.04250e",
                "49e810b6.0cfd3"
            ]
        ]
    },
    {
        "id": "56929f0c.9c681",
        "type": "mqtt out",
        "z": "da904081.4e064",
        "name": "HEATER ON/OFF",
        "topic": "/home/Thermostat/relay/0/set",
        "qos": "",
        "retain": "",
        "broker": "a41e02ae.5c398",
        "x": 1096.3213119506836,
        "y": 549.5357418060303,
        "wires": []
    },
    {
        "id": "81b5d072.aa892",
        "type": "function",
        "z": "da904081.4e064",
        "name": "Convertir Mensaje TEMP",
        "func": "msg.payload = parseFloat(msg.payload);\nmsg.topic = 'sensor_temperature';\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 492.2261734008789,
        "y": 170.11905670166016,
        "wires": [
            [
                "ded5ab1.95be358",
                "d15df687.45c468"
            ]
        ]
    },
    {
        "id": "ded5ab1.95be358",
        "type": "debug",
        "z": "da904081.4e064",
        "name": "Temp Sensor",
        "active": true,
        "console": "false",
        "complete": "payload",
        "x": 754.619010925293,
        "y": 45.42859649658203,
        "wires": []
    },
    {
        "id": "57f50795.dcd598",
        "type": "ui_gauge",
        "z": "da904081.4e064",
        "name": "Gauge: Temp",
        "group": "f9325f56.6c0ed",
        "order": 1,
        "width": 0,
        "height": 0,
        "gtype": "gage",
        "title": "Temperatura",
        "label": "units",
        "format": "{{value}} ºC",
        "min": "10",
        "max": "40",
        "colors": [
            "#ffe700",
            "#e68b00",
            "#ff0000"
        ],
        "seg1": "20",
        "seg2": "30",
        "x": 411.75,
        "y": 40,
        "wires": []
    },
    {
        "id": "56720eec.3da18",
        "type": "ui_gauge",
        "z": "da904081.4e064",
        "name": "Gauge: Hum",
        "group": "f9325f56.6c0ed",
        "order": 2,
        "width": 0,
        "height": 0,
        "gtype": "gage",
        "title": "Humedad",
        "label": "units",
        "format": "{{value}}",
        "min": 0,
        "max": "100",
        "colors": [
            "#00b500",
            "#e6e600",
            "#ca3838"
        ],
        "seg1": "45",
        "seg2": "70",
        "x": 402.8333435058594,
        "y": 557.4999992847443,
        "wires": []
    },
    {
        "id": "6a09073.47b18f8",
        "type": "mqtt in",
        "z": "da904081.4e064",
        "name": "SENSOR AM2302-HUM",
        "topic": "/home/Thermostat/humidity",
        "qos": "2",
        "broker": "a41e02ae.5c398",
        "x": 154.047607421875,
        "y": 534.5714390277863,
        "wires": [
            [
                "56720eec.3da18",
                "74c0c78a.1f64e8"
            ]
        ]
    },
    {
        "id": "192cca65.5e9e96",
        "type": "ui_chart",
        "z": "da904081.4e064",
        "name": "",
        "group": "7c31da65.7796a4",
        "order": 2,
        "width": 0,
        "height": 0,
        "label": "Humedad",
        "chartType": "line",
        "legend": "true",
        "xformat": "HH:mm",
        "interpolate": "linear",
        "nodata": "",
        "dot": false,
        "ymin": "30",
        "ymax": "70",
        "removeOlder": "3",
        "removeOlderPoints": "",
        "removeOlderUnit": "3600",
        "cutout": 0,
        "useOneColor": false,
        "colors": [
            "#1f77b4",
            "#aec7e8",
            "#ff7f0e",
            "#2ca02c",
            "#98df8a",
            "#d62728",
            "#ff9896",
            "#9467bd",
            "#c5b0d5"
        ],
        "useOldStyle": false,
        "x": 378.3333282470703,
        "y": 619.9999916553497,
        "wires": [
            [],
            []
        ]
    },
    {
        "id": "26941838.3c1f98",
        "type": "change",
        "z": "da904081.4e064",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "topic",
                "pt": "msg",
                "to": "Temperatura",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 494.61902618408203,
        "y": 91.95237159729004,
        "wires": [
            [
                "eee08de0.0d78a"
            ]
        ]
    },
    {
        "id": "74c0c78a.1f64e8",
        "type": "change",
        "z": "da904081.4e064",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "topic",
                "pt": "msg",
                "to": "Humedad",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 208.3333282470703,
        "y": 619.9999916553497,
        "wires": [
            [
                "192cca65.5e9e96"
            ]
        ]
    },
    {
        "id": "ad50a979.98c8c8",
        "type": "ui_template",
        "z": "da904081.4e064",
        "group": "95e5f821.53ff28",
        "name": "Nest",
        "order": 1,
        "width": "6",
        "height": "6",
        "format": "<div id=\"thermostat\"></div>\n\n<style>\n@import url(http://fonts.googleapis.com/css?family=Open+Sans:300);\n#thermostat {\n  margin: 0 auto;\n  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\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.away .dial__ico__leaf {\n  visibility: hidden;\n}\n.dial.away .dial__lbl--target {\n  visibility: hidden;\n}\n.dial.away .dial__lbl--target--half {\n  visibility: hidden;\n}\n.dial.away .dial__lbl--away {\n  opacity: 1;\n}\n.dial .dial__shape {\n  -webkit-transition: fill 0.5s;\n  transition: fill 0.5s;\n}\n.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: white;\n  fill-rule: evenodd;\n  opacity: 0;\n  -webkit-transition: opacity 0.5s;\n  transition: opacity 0.5s;\n}\n.dial--edit .dial__editableIndicator {\n  opacity: 1;\n}\n.dial--state--off .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__ticks path {\n  fill: rgba(255, 255, 255, 0.3);\n}\n.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--away {\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\tfunction roundHalf(num) {\n\t\treturn Math.round(num*2)/2;\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 || 400,\n\t\t\tminValue: options.minValue || 10, // Minimum value for target temperature\n\t\t\tmaxValue: options.maxValue || 30, // 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: ['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\taway: 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\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 = !!val;\n\t\t\t\trender();\n\t\t\t}\n\t\t});\n\t\tObject.defineProperty(this,'away',{\n\t\t\tget: function() {\n\t\t\t\treturn state.away;\n\t\t\t},\n\t\t\tset: function(val) {\n\t\t\t\tstate.away = !!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: '100%', //options.diameter+'px',\n\t\t\theight: '100%', //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 lblAway = createSVGElement('text',{\n\t\t\tx: properties.radius,\n\t\t\ty: properties.radius,\n\t\t\tclass: 'dial__lbl dial__lbl--away'\n\t\t},svg);\n\t\tvar lblAway_text = document.createTextNode('AWAY');\n\t\tlblAway.appendChild(lblAway_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\trenderAway();\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.away) {\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 = Math.floor(self.ambient_temperature);\n\t\t\tif (self.ambient_temperature%1!=0) {\n\t\t\t\tlblAmbient_text.nodeValue += '⁵';\n\t\t\t}\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',self.target_temperature%1!=0);\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 - away\n\t\t */\n\t\tfunction renderAway() {\n\t\t\tsvg.classList[self.away ? 'add' : 'remove']('away');\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/* ==== */\n(function(scope) {\n    \n    var nest = new thermostatDial(document.getElementById('thermostat'),{\n    \tonSetTargetTemperature: function(v) {\n    \t\tscope.send({topic: \"target_temperature\", payload: v});\n    \t}\n    });\n\n\n    scope.$watch('msg', function(data) {\n        //console.log(data.topic+\"  \"+data.payload);\n        if (data.topic == \"ambient_temperature\") {\n            nest.ambient_temperature = data.payload;\n        } if (data.topic == \"target_temperature\") {\n            nest.target_temperature = data.payload;\n        } if (data.topic == \"hvac_state\") {\n            nest.hvac_state = data.payload;\n        } if (data.topic == \"has_leaf\") {\n            nest.has_leaf = data.payload;\n        } if (data.topic == \"away\") {\n            nest.away = data.payload;\n        }\n    });\n})(scope);\n\n</script>",
        "storeOutMessages": true,
        "fwdInMessages": false,
        "templateScope": "local",
        "x": 634.5238075256348,
        "y": 442.8571138381958,
        "wires": [
            [
                "f362e7cb.90b8a8"
            ]
        ]
    },
    {
        "id": "182fb20b.04250e",
        "type": "function",
        "z": "da904081.4e064",
        "name": "ambient_temperature",
        "func": "msg.topic = \"ambient_temperature\";\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 357.6190490722656,
        "y": 308.33333587646484,
        "wires": [
            [
                "ad50a979.98c8c8"
            ]
        ]
    },
    {
        "id": "9e25b1e0.00ed9",
        "type": "function",
        "z": "da904081.4e064",
        "name": "hvac_state",
        "func": "global.set(\"color-state\",msg.payload);\n\nmsg.topic = \"hvac_state\";\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 600.714241027832,
        "y": 721.4285278320312,
        "wires": [
            [
                "ad50a979.98c8c8"
            ]
        ]
    },
    {
        "id": "f362e7cb.90b8a8",
        "type": "function",
        "z": "da904081.4e064",
        "name": "target_temperature",
        "func": "global.set(\"room-target\",msg.payload);\n\nif (msg.topic == \"target_temperature\") {\nreturn msg;\n}",
        "outputs": 1,
        "noerr": 0,
        "x": 830,
        "y": 660,
        "wires": [
            [
                "c97472f8.396e1",
                "d15df687.45c468",
                "a2df08f6.45cbb8"
            ]
        ]
    },
    {
        "id": "c97472f8.396e1",
        "type": "debug",
        "z": "da904081.4e064",
        "name": "Target Temp",
        "active": true,
        "console": "false",
        "complete": "payload",
        "x": 1082.142921447754,
        "y": 710.476203918457,
        "wires": []
    },
    {
        "id": "d15df687.45c468",
        "type": "function",
        "z": "da904081.4e064",
        "name": "Comparar TEMPs",
        "func": "context.target = context.target || 0.0;\ncontext.sensor = context.sensor || 0.0;\n\nif (msg.topic === 'sensor_temperature') {\n  context.sensor = msg.payload;\n} else if (msg.topic === 'target_temperature') {\n  context.target = msg.payload;\n} \n\nif (context.target >= context.sensor) {\n  return {payload: 1};\n} else {\n  return {payload: 0};\n}",
        "outputs": 1,
        "noerr": 0,
        "x": 834.8214263916016,
        "y": 427.14288997650146,
        "wires": [
            [
                "56929f0c.9c681",
                "850d28fd.fbcd68"
            ]
        ]
    },
    {
        "id": "850d28fd.fbcd68",
        "type": "function",
        "z": "da904081.4e064",
        "name": "Estado Color Nest",
        "func": "msg.topic = \"hvac_state\";\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 750,
        "y": 240,
        "wires": [
            [
                "169d3283.26605d",
                "7123dcdb.01b5e4"
            ]
        ]
    },
    {
        "id": "169d3283.26605d",
        "type": "debug",
        "z": "da904081.4e064",
        "name": "STATE",
        "active": false,
        "console": "false",
        "complete": "payload",
        "x": 950,
        "y": 180,
        "wires": []
    },
    {
        "id": "7123dcdb.01b5e4",
        "type": "switch",
        "z": "da904081.4e064",
        "name": "",
        "property": "payload",
        "propertyType": "msg",
        "rules": [
            {
                "t": "eq",
                "v": "0",
                "vt": "str"
            },
            {
                "t": "eq",
                "v": "1",
                "vt": "str"
            }
        ],
        "checkall": "true",
        "outputs": 2,
        "x": 910,
        "y": 300,
        "wires": [
            [
                "9b5a6f7.266c29"
            ],
            [
                "72c6507e.b6579"
            ]
        ]
    },
    {
        "id": "9b5a6f7.266c29",
        "type": "template",
        "z": "da904081.4e064",
        "name": "Calefacción OFF",
        "field": "payload",
        "fieldType": "msg",
        "format": "handlebars",
        "syntax": "mustache",
        "template": "off",
        "output": "str",
        "x": 1082.8571586608887,
        "y": 268.5713920593262,
        "wires": [
            [
                "9e25b1e0.00ed9",
                "651008fb.7547b8"
            ]
        ]
    },
    {
        "id": "72c6507e.b6579",
        "type": "template",
        "z": "da904081.4e064",
        "name": "Calefacción ON",
        "field": "payload",
        "fieldType": "msg",
        "format": "handlebars",
        "syntax": "mustache",
        "template": "heating",
        "output": "str",
        "x": 1075.714370727539,
        "y": 371.4285821914673,
        "wires": [
            [
                "9e25b1e0.00ed9"
            ]
        ]
    },
    {
        "id": "651008fb.7547b8",
        "type": "debug",
        "z": "da904081.4e064",
        "name": "SALIDA",
        "active": false,
        "console": "false",
        "complete": "payload",
        "x": 1203.5713729858398,
        "y": 162.14287090301514,
        "wires": []
    },
    {
        "id": "a2df08f6.45cbb8",
        "type": "change",
        "z": "da904081.4e064",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "topic",
                "pt": "msg",
                "to": "Deseada",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 1070,
        "y": 640,
        "wires": [
            [
                "bf4ac0a5.19532"
            ]
        ]
    },
    {
        "id": "eee08de0.0d78a",
        "type": "ui_chart",
        "z": "da904081.4e064",
        "name": "",
        "group": "7c31da65.7796a4",
        "order": 1,
        "width": 0,
        "height": 0,
        "label": "Temperatura",
        "chartType": "line",
        "legend": "true",
        "xformat": "HH:mm:ss",
        "interpolate": "linear",
        "nodata": "",
        "dot": false,
        "ymin": "10",
        "ymax": "30",
        "removeOlder": "3",
        "removeOlderPoints": "",
        "removeOlderUnit": "3600",
        "cutout": 0,
        "useOneColor": false,
        "colors": [
            "#1f77b4",
            "#aec7e8",
            "#ff7f0e",
            "#2ca02c",
            "#98df8a",
            "#d62728",
            "#ff9896",
            "#9467bd",
            "#c5b0d5"
        ],
        "useOldStyle": false,
        "x": 772.8571428571428,
        "y": 112.85714285714285,
        "wires": [
            [],
            []
        ]
    },
    {
        "id": "bf4ac0a5.19532",
        "type": "ui_chart",
        "z": "da904081.4e064",
        "name": "",
        "group": "95e5f821.53ff28",
        "order": 0,
        "width": 0,
        "height": 0,
        "label": "Tempº Deseada",
        "chartType": "line",
        "legend": "true",
        "xformat": "HH:mm:ss",
        "interpolate": "linear",
        "nodata": "",
        "dot": false,
        "ymin": "10",
        "ymax": "30",
        "removeOlder": "3",
        "removeOlderPoints": "",
        "removeOlderUnit": "3600",
        "cutout": 0,
        "useOneColor": false,
        "colors": [
            "#1f77b4",
            "#aec7e8",
            "#ff7f0e",
            "#2ca02c",
            "#98df8a",
            "#d62728",
            "#ff9896",
            "#9467bd",
            "#c5b0d5"
        ],
        "useOldStyle": false,
        "x": 1304.2857142857142,
        "y": 634.2857142857142,
        "wires": [
            [],
            []
        ]
    },
    {
        "id": "6ba07343.29a95c",
        "type": "ui_ui_control",
        "z": "da904081.4e064",
        "name": "ui change",
        "x": 86.66666412353516,
        "y": 338.33330726623535,
        "wires": [
            [
                "827ff685.e970e8"
            ]
        ]
    },
    {
        "id": "827ff685.e970e8",
        "type": "delay",
        "z": "da904081.4e064",
        "name": "",
        "pauseType": "delay",
        "timeout": "1",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "x": 176.6666717529297,
        "y": 419.9999809265137,
        "wires": [
            [
                "78adbba4.e5bbe4"
            ]
        ]
    },
    {
        "id": "78adbba4.e5bbe4",
        "type": "function",
        "z": "da904081.4e064",
        "name": "global target-temp",
        "func": "msg.payload = global.get(\"room-target\");\nmsg.topic = 'target_temperature';\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 361.6666793823242,
        "y": 468.33334159851074,
        "wires": [
            [
                "575a336e.a3b5ac",
                "ad50a979.98c8c8"
            ]
        ]
    },
    {
        "id": "575a336e.a3b5ac",
        "type": "debug",
        "z": "da904081.4e064",
        "name": "global-target-temp",
        "active": true,
        "console": "false",
        "complete": "payload",
        "x": 351.6667251586914,
        "y": 371.6667528152466,
        "wires": []
    },
    {
        "id": "fab41969.1480f8",
        "type": "ui_ui_control",
        "z": "da904081.4e064",
        "name": "ui change",
        "x": 116.66666412353516,
        "y": 714.9999694824219,
        "wires": [
            [
                "abb28c3b.d52d1"
            ]
        ]
    },
    {
        "id": "abb28c3b.d52d1",
        "type": "delay",
        "z": "da904081.4e064",
        "name": "",
        "pauseType": "delay",
        "timeout": "1",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "x": 249.99998474121094,
        "y": 783.3332862854004,
        "wires": [
            [
                "485b77e9.f539c8"
            ]
        ]
    },
    {
        "id": "485b77e9.f539c8",
        "type": "function",
        "z": "da904081.4e064",
        "name": "global color-state",
        "func": "msg.payload = global.get(\"color-state\");\nmsg.topic = \"hvac_state\";\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 359.99998474121094,
        "y": 688.3333435058594,
        "wires": [
            [
                "ad50a979.98c8c8",
                "61f53c5f.e4f1e4"
            ]
        ]
    },
    {
        "id": "61f53c5f.e4f1e4",
        "type": "debug",
        "z": "da904081.4e064",
        "name": "global-color-state",
        "active": true,
        "console": "false",
        "complete": "payload",
        "x": 509.99998474121094,
        "y": 786.6666984558105,
        "wires": []
    },
    {
        "id": "49e810b6.0cfd3",
        "type": "function",
        "z": "da904081.4e064",
        "name": "Leaf",
        "func": "minleaf = 18;\nmaxleaf = 22;\ntemperature = msg.payload;\nmsg.payload=false;\nif (temperature >= minleaf){\n    if (temperature <= maxleaf){\n        msg.payload = true;\n    }\n}\nmsg.topic = \"has_leaf\";\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 444.9999771118164,
        "y": 233.3333396911621,
        "wires": [
            [
                "ad50a979.98c8c8"
            ]
        ]
    }
]

Credits

Alex Corvis

Alex Corvis

1 project • 15 followers
BSc in Telecommunications. Technologies Enthusiast Maker - Learning everyday something new about IOT, Arduino, RaspberryPi, 3Dprint, RC
Thanks to Andrius, Dal Hundal, and Xose Perez.

Comments