Welcome to Hackster!
Hackster is a community dedicated to learning hardware, from beginner to pro. Join us, it's free!
NotEnoughTech
Published © CC BY

NEST Your Old Thermostat for Under $5

Make your old thermostat smart again with this $5 device!

IntermediateFull instructions provided2 hours11,060
NEST Your Old Thermostat for Under $5

Things used in this project

Hardware components

Sonoff Basic
Itead Sonoff Basic
×1
DHT11 Temperature & Humidity Sensor (3 pins)
DHT11 Temperature & Humidity Sensor (3 pins)
×1

Software apps and online services

Node-RED on IBM Cloud
Node-RED on IBM Cloud
tasmota firmware

Story

Read more

Schematics

Connection Schematics

Here is how I wired Sonoff with the Thermostat

Code

NodeRED Nest Flow

HTML
This is the complete flow with Alexa support. Just import this through NodeRED
[{"id":"293ca76e.bb4f38","type":"ui_template","z":"3d4c38d8.c679f8","group":"dfad00fc.e2b86","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":false,"fwdInMessages":false,"templateScope":"local","x":830,"y":315,"wires":[[]]},{"id":"bec0c6fd.3116b8","type":"mqtt in","z":"3d4c38d8.c679f8","name":"Read Temp DHT11","topic":"sonoff/tele/SENSOR","qos":"0","broker":"4a31cd84.72cbd4","x":150,"y":105,"wires":[["5b69274c.cdcbd8"]]},{"id":"5b69274c.cdcbd8","type":"json","z":"3d4c38d8.c679f8","name":"","property":"payload","action":"","pretty":false,"x":305,"y":105,"wires":[["155a1d57.c32543"]]},{"id":"8ed6c05f.ee25f","type":"ui_slider","z":"3d4c38d8.c679f8","name":"Slider","label":"Target Temp","group":"dfad00fc.e2b86","order":14,"width":0,"height":0,"passthru":false,"topic":"slider","min":"10","max":"30","step":1,"x":365,"y":405,"wires":[["692dc6f1.662638"]]},{"id":"7ed1b2d6.6fdc7c","type":"function","z":"3d4c38d8.c679f8","name":"Process Alexa responses","func":"if (msg.command === \"GetTemperatureReadingRequest\"){\n    x =flow.get('TempTarget');\n    msg.extra = {\n    \"temperatureReading\": {\n        \"value\": x},\n    \"applianceResponseTimestamp\": new Date().toISOString()};\n    msg.payload = true;\n    return msg;\n}\n\nif (msg.command === \"SetTargetTemperatureRequest\"){\nif (msg.payload < 10 || msg.payload > 30) {\n    var range = {\n        min: 10.0,\n        max: 30.0\n    }\n    msg.payload = false;\n    msg.extra = range;\n} \nelse {\n    msg.extra = {\n        targetTemperature: {\n            value: msg.payload\n        }\n    };\n    msg.payload = true;\n}\nreturn msg;\n}\n\nif (msg.command === \"TurnOnRequest\"){\n    msg.payload = true;\n    flow.set('away', false);\n    flow.set('TempTarget', 21);\n    return msg;\n    \n}\n\nif (msg.command === \"TurnOffRequest\"){\n    msg.payload = true;\n    flow.set('away', true);\n    \n    return msg;\n    \n    \n}","outputs":1,"noerr":0,"x":485,"y":615,"wires":[["f5ce638c.dc8bb"]]},{"id":"ace4aa04.d4e008","type":"function","z":"3d4c38d8.c679f8","name":"Leaf Control","func":"x = flow.get('TempAmbient');\n\nif (x > 17 && x < 23){\n     flow.set('leaf', true);\n    msg.payload = true;\n    msg.topic = \"has_leaf\";\n    return msg;\n}\n    \nelse{flow.set('leaf', false);\n    msg.payload = false;\n    msg.topic = \"has_leaf\";\n    return msg;\n}\n\n","outputs":1,"noerr":0,"x":520,"y":270,"wires":[["293ca76e.bb4f38"]]},{"id":"d4236beb.9243d8","type":"function","z":"3d4c38d8.c679f8","name":"Color ","func":"x = flow.get('TempTarget');   //target\nz = flow.get('TempAmbient');  //ambient\n\nif (z < x){\n    flow.set('heatingState', \"heating\");\n    flow.set('heatingSwitch', \"ON\");\n}\nif (z >= x){\n    flow.set('heatingState', \"off\");\n    flow.set('heatingSwitch', \"OFF\");\n}\nmsg.payload = z;\nmsg.topic = \"ambient_temperature\";\nreturn msg;","outputs":1,"noerr":0,"x":500,"y":225,"wires":[["293ca76e.bb4f38"]]},{"id":"45fd6002.8f39b","type":"mqtt out","z":"3d4c38d8.c679f8","name":"Control Sonoff","topic":"sonoff/cmnd/POWER1","qos":"","retain":"","broker":"4a31cd84.72cbd4","x":755,"y":555,"wires":[]},{"id":"ba4bb151.fd715","type":"function","z":"3d4c38d8.c679f8","name":"Control relay","func":"if (msg.command === \"TurnOffRequest\"){\n    msg.payload = \"OFF\";\n    return msg;\n}\n\nif (msg.command === \"TurnOnRequest\"){\n    msg.payload = \"ON\";\n    flow.set('TempTarget', 21);\n    return msg;\n}\nif (msg.topic === \"update\"){\n    msg.payload = flow.get('heatingSwitch');\n}\nreturn msg;","outputs":1,"noerr":0,"x":520,"y":555,"wires":[["45fd6002.8f39b"]]},{"id":"155a1d57.c32543","type":"smooth","z":"3d4c38d8.c679f8","name":"Average temp","property":"payload.DHT11.Temperature","action":"mean","count":"15","round":"2","mult":"single","x":470,"y":105,"wires":[["79bc5ce0.0442f4"]]},{"id":"89762fd3.ef445","type":"alexa-home","z":"3d4c38d8.c679f8","conf":"df965cf5.93857","device":"35103","acknoledge":false,"name":"","topic":"Heating","x":220,"y":615,"wires":[["7ed1b2d6.6fdc7c","ba4bb151.fd715","692dc6f1.662638"]]},{"id":"f5ce638c.dc8bb","type":"alexa-home-resp","z":"3d4c38d8.c679f8","x":775,"y":615,"wires":[]},{"id":"4c13797d.961898","type":"function","z":"3d4c38d8.c679f8","name":"colour -away override","func":"x = flow.get('away');\n\nif (x === true){\n    msg.topic = \"hvac_state\";\n    msg.payload = \"off\";\n    return msg;\n}\n\nmsg.topic = \"hvac_state\";\nmsg.payload = flow.get('heatingState');\n\nreturn msg;","outputs":1,"noerr":0,"x":550,"y":315,"wires":[["293ca76e.bb4f38"]]},{"id":"aefa46a2.f36038","type":"function","z":"3d4c38d8.c679f8","name":"Away Control","func":"x = flow.get('away');\n\nif (x === true){\n    flow.set('heatingSwitch', \"OFF\");\n    flow.set('heatingState', \"off\");\n}\n\nmsg.topic = \"away\";\nmsg.payload = flow.get('away');\nreturn msg;\n\n","outputs":1,"noerr":0,"x":520,"y":360,"wires":[["293ca76e.bb4f38"]]},{"id":"692dc6f1.662638","type":"function","z":"3d4c38d8.c679f8","name":"set temp target","func":"if (msg.topic === \"update\"){\n    msg.topic = \"target_temperature\";\n    msg.payload = flow.get('TempTarget');\n    return msg;\n}\n\nif (msg.command === \"SetTargetTemperatureRequest\") {\n    flow.set('away', false);\n    msg.topic = \"target_temperature\";\n    flow.set('TempTarget', msg.payload);\n    \n}\n\nif (msg.topic === \"slider\") {\n    flow.set('away', false);\n    msg.topic = \"target_temperature\";\n    flow.set('TempTarget', msg.payload);\n}\n\nif (msg.command === \"GetTemperatureReadingRequest\"){}\n\nreturn msg;","outputs":1,"noerr":0,"x":530,"y":405,"wires":[["293ca76e.bb4f38"]]},{"id":"f3aba7f.f852c58","type":"inject","z":"3d4c38d8.c679f8","name":"Refresh every 5 sec","topic":"update","payload":"upate","payloadType":"str","repeat":"5","crontab":"","once":false,"onceDelay":0.1,"x":190,"y":225,"wires":[["4c13797d.961898","aefa46a2.f36038","692dc6f1.662638","ba4bb151.fd715","d4236beb.9243d8","ace4aa04.d4e008","b3981462.98d958"]]},{"id":"b3981462.98d958","type":"function","z":"3d4c38d8.c679f8","name":"Update slider","func":"msg.payload = flow.get('TempTarget');\nreturn msg;","outputs":1,"noerr":0,"x":220,"y":405,"wires":[["8ed6c05f.ee25f"]]},{"id":"53430ab1.c29204","type":"function","z":"3d4c38d8.c679f8","name":"test","func":"msg.ambinet = flow.get('TempAmbient');\nmsg.target = flow.get('TempTarget');\nmsg.leaf = flow.get('leaf');\nmsg.state = flow.get('heatingState');\nmsg.switch = flow.get('heatingSwitch');\nmsg.away = flow.get('away');\nreturn msg;","outputs":1,"noerr":0,"x":410,"y":720,"wires":[["198d2d47.f28653"]]},{"id":"198d2d47.f28653","type":"debug","z":"3d4c38d8.c679f8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":590,"y":720,"wires":[]},{"id":"fb78fd8b.0ca8a","type":"inject","z":"3d4c38d8.c679f8","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":240,"y":720,"wires":[["53430ab1.c29204"]]},{"id":"79bc5ce0.0442f4","type":"function","z":"3d4c38d8.c679f8","name":"Update the 'TempAmbient'","func":"flow.set('TempAmbient', msg.payload.DHT11.Temperature);\nreturn msg;","outputs":1,"noerr":0,"x":695,"y":105,"wires":[[]]},{"id":"e31f1fb1.091a2","type":"comment","z":"3d4c38d8.c679f8","name":"Measure DHT11 temp and update var","info":"Gets the Temp, averages last X readings \nand updates the variable for the next push","x":210,"y":60,"wires":[]},{"id":"12d4c524.ac0acb","type":"comment","z":"3d4c38d8.c679f8","name":"Update Dial ","info":"","x":130,"y":180,"wires":[]},{"id":"bca5ef19.3f92f","type":"comment","z":"3d4c38d8.c679f8","name":"Control Relay","info":"","x":520,"y":510,"wires":[]},{"id":"7cb30851.a329a8","type":"comment","z":"3d4c38d8.c679f8","name":"Alexa Responses","info":"","x":230,"y":570,"wires":[]},{"id":"8f8745c.fc8afb8","type":"comment","z":"3d4c38d8.c679f8","name":"Test the Flow Vars","info":"","x":240,"y":675,"wires":[]},{"id":"dfad00fc.e2b86","type":"ui_group","z":"","name":"Almost Like Nest but Cheaper","tab":"4447df43.a3ad4","order":2,"disp":true,"width":"6","collapse":false},{"id":"4a31cd84.72cbd4","type":"mqtt-broker","z":"","name":"","broker":"192.168.1.183","port":"1883","clientid":"","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""},{"id":"df965cf5.93857","type":"alexa-home-conf","z":"","username":"quintaar"},{"id":"4447df43.a3ad4","type":"ui_tab","z":"","name":"Show","icon":"dashboard"}]

Credits

NotEnoughTech
10 projects • 82 followers
I'm Mat I play with gadgets, make new stuff and automate pretty much everything that requires repeating more than once!
Contact
Thanks to Dal Hundall.

Comments

Please log in or sign up to comment.