Autor Thema: Node-Red als Frontend  (Gelesen 8200 mal)

Offline Master_Nick

  • Sr. Member
  • ****
  • Beiträge: 550
Antw:Node-Red als Frontend
« Antwort #60 am: 12 März 2018, 13:13:22 »
Langsam will ich nicht sagen, es hat 2 Sekunden Ladezeit, bis die Anzeige der Seite steht. Die Seiten die nur Switches haben gehen deutlich fixer. Es ist jammern auf hohem Niveau :-D und geht echt nur um die Ladezeit.

Gehostet habe ich Node-RED auf einem Pi3 und die Anzeige erfolgt auf einem anderen Pi3, PC oder Handy.
« Letzte Änderung: 12 März 2018, 13:16:29 von Master_Nick »
RasPi 3 mit nanoCUL (a-culfw) | IT(V1&V3), IT-PIR, THGR122NX |Co² | alexa-fhem 0.3.0 | WOL | NFC | Harmony UltimateHub | Homestate | Roomba | 10" Touch mit Node-Red | SonOff S20

Offline Shojo

  • Full Member
  • ***
  • Beiträge: 273
  • Wie, Was, Wo?
    • DER BASTELBUNKER / Meine kleinen Projekte…
Antw:Node-Red als Frontend
« Antwort #61 am: 12 März 2018, 13:18:08 »
Ok, ich habe da einen Odroid C2 mit NandFlash hinter der ist da einiges Performanter.
Odroid-C2, Signalduino 433mHz, SignalESP 433mHz, nanoCUL 868mHz, HM-MOD-RPI-PCB, ESP8266, SONOFF, Sonos, Echo Dot, Xiaomi Vacuum, ESP RGBWW Wifi Led Controller (vbs Edition), Node-RED, MQTT, SKBB.LEDMatrix

Offline Master_Nick

  • Sr. Member
  • ****
  • Beiträge: 550
Antw:Node-Red als Frontend
« Antwort #62 am: 12 März 2018, 13:22:01 »
 ;D Dann liegt es evtl daran.  8)
RasPi 3 mit nanoCUL (a-culfw) | IT(V1&V3), IT-PIR, THGR122NX |Co² | alexa-fhem 0.3.0 | WOL | NFC | Harmony UltimateHub | Homestate | Roomba | 10" Touch mit Node-Red | SonOff S20

Offline Shojo

  • Full Member
  • ***
  • Beiträge: 273
  • Wie, Was, Wo?
    • DER BASTELBUNKER / Meine kleinen Projekte…
Antw:Node-Red als Frontend
« Antwort #63 am: 07 April 2018, 13:05:33 »
Hier noch mal ein Ansatz von mir.

Jeder Schalter oder Regler bekommt von mir als Topic das Fhem Device mitgegeben.
Wenn ich aber was anderes wie den State schalten möchte z.B. den HSV Wert sieht das so aus WZ.Licht.RGB.Fensterfront hvs
Hier noch die kleine Logik des Fhem Messages bauen:
var temp = msg.payload;
if (typeof temp == "string" && temp.startsWith("hsv"))
{
    temp = temp.replace(/ /g,"");
    temp = temp.replace(/%/g,"");
    temp = temp.replace(/hsv\(/g,"");
    temp = temp.replace(/\)/g,"");
    var x = temp.split(",");
    temp =  x[0] + "," + x[1] + "," + x[2];
}

msg.payload = "set " +  msg.topic + " " + temp;
return msg;

Und das Fhem Device:
defmod MQTT.NodeRed.Input MQTT_DEVICE
attr MQTT.NodeRed.Input IODev MQTTBroker
attr MQTT.NodeRed.Input stateFormat transmission-state
attr MQTT.NodeRed.Input subscribeReading_cmd {fhem("$message")} NodeRed/ToFHEM/cmd qos:1
« Letzte Änderung: 07 April 2018, 13:09:20 von Shojo »
Odroid-C2, Signalduino 433mHz, SignalESP 433mHz, nanoCUL 868mHz, HM-MOD-RPI-PCB, ESP8266, SONOFF, Sonos, Echo Dot, Xiaomi Vacuum, ESP RGBWW Wifi Led Controller (vbs Edition), Node-RED, MQTT, SKBB.LEDMatrix

Offline Master_Nick

  • Sr. Member
  • ****
  • Beiträge: 550
Antw:Node-Red als Frontend
« Antwort #64 am: 12 April 2018, 23:12:42 »
Ich habe meine gesamte Thermostatgeschichte nun auf Nest Optik gezogen die Benutzbarkeit ist hier viel besser als bei einem Schieberegler.  8) ;D

Finde es richtig geil!
Habe allerdings mit den hier gelieferten Dingen nur teils was arbeiten können - die hatten beim Reload immer noch Probleme bei mir :-)
Habe das Ganze beim Ursprung des Nest Thermostats für NodeRed dann gefunden und mit Ideen von dort gelöst... krieg es schon kaum noch auf die Kette was wie wo... viel Arbeit/Lesen gewesen.
Habe dann noch einige Icons eingebunden und bin sehr zufrieden.

Auch wird hier die Temperatur verglichen und entschieden ob geheizt wird (rotes Nest Thermostat). Und das Blatt kommt bei gewissen Zieltemperaturen die man als "sparend" betrachten könnte (kann man alles selber anpassen). Ansonsten habe ich meinen VOC Sensor im Wohnzimmer noch mit eingebunden ohne Werte sondern mittels 3er Symbole: Daumen hoch grün, Daumen hoch gelb und Daumen runter rot. Auch Window Open wird angezeigt wenn der Wert auf 1 steht - gelber Text "Window" erscheint dann.

Bei mehr als einem Thermostat ist es wichtig das DIV umzubenennen und Im weiteren Code den Namen der vorher ganz oben im DIV Stand erneut zu ändern.
Ich erweitere noch um ein eingeblendetes Fenstersymbol.

Code für NodeRed für das Wohnzimmer Thermostat:
[{"id":"53e6b42a.aabbe4","type":"function","z":"15bc8d4b.90af3b","name":"Konvertieren der Temperatur","func":"msg.payload = parseFloat(msg.payload);\nmsg.topic = 'sensor_temperature';\nnode.status({text:msg.payload + \"°C\"});\nreturn msg;","outputs":1,"noerr":0,"x":810,"y":300,"wires":[["730bb5a.9f6bfcc","c0dc329a.4f59b"]]},{"id":"730bb5a.9f6bfcc","type":"debug","z":"15bc8d4b.90af3b","name":"Temp Sensor","active":false,"console":"false","complete":"payload","x":1130,"y":300,"wires":[]},{"id":"63e04c4d.bb5f6c","type":"ui_template","z":"15bc8d4b.90af3b","group":"d2bea201.d68888","name":"Nest","order":5,"width":"6","height":"6","format":"<div id=\"thermostat-WZ\"></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-WZ'),{\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":1030,"y":540,"wires":[["228ed0cb.d3b908"]]},{"id":"773154d5.ab0824","type":"function","z":"15bc8d4b.90af3b","name":"ambient_temperature","func":"msg.topic = \"ambient_temperature\";\nglobal.set(\"wz-ambient\",msg.payload);\nnode.status({text:msg.payload + \"°C\"});\nreturn msg;","outputs":1,"noerr":0,"x":777.6190490722656,"y":388.33333587646484,"wires":[["63e04c4d.bb5f6c"]]},{"id":"1ae634e.7e416cb","type":"function","z":"15bc8d4b.90af3b","name":"hvac_state","func":"global.set(\"wz-state\",msg.payload);\n\nmsg.topic = \"hvac_state\";\nnode.status({text:msg.payload});\nreturn msg;","outputs":1,"noerr":0,"x":1670,"y":340,"wires":[["63e04c4d.bb5f6c"]]},{"id":"228ed0cb.d3b908","type":"function","z":"15bc8d4b.90af3b","name":"target_temperature","func":"if (msg.topic == \"target_temperature\") {\nglobal.set(\"wz-target\",msg.payload);\nreturn msg;\n}","outputs":1,"noerr":0,"x":1210,"y":540,"wires":[["c0dc329a.4f59b","2ab0e1e3.ac211e"]]},{"id":"c0dc329a.4f59b","type":"function","z":"15bc8d4b.90af3b","name":"Temperaturen Vergleichen","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}\nnode.status({text:msg.payload});","outputs":1,"noerr":0,"x":1460,"y":500,"wires":[["7dc8f30f.3d7064"]]},{"id":"7dc8f30f.3d7064","type":"function","z":"15bc8d4b.90af3b","name":"Farbstatus Nest","func":"msg.topic = \"hvac_state\";\nnode.status({text:msg.payload});\nreturn msg;","outputs":1,"noerr":0,"x":1700,"y":500,"wires":[["74d1ff15.059858"]]},{"id":"74d1ff15.059858","type":"switch","z":"15bc8d4b.90af3b","name":"","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"0","vt":"str"},{"t":"eq","v":"1","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":1310,"y":360,"wires":[["3d83fd16.3def3a"],["42df8b17.773ac4"]]},{"id":"3d83fd16.3def3a","type":"template","z":"15bc8d4b.90af3b","name":"Heizen OFF","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"off","output":"str","x":1490,"y":300,"wires":[["1ae634e.7e416cb"]]},{"id":"42df8b17.773ac4","type":"template","z":"15bc8d4b.90af3b","name":"Heizen ON","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"heating","output":"str","x":1490,"y":380,"wires":[["1ae634e.7e416cb"]]},{"id":"bcc9c2f.902a54","type":"delay","z":"15bc8d4b.90af3b","name":"","pauseType":"delay","timeout":"50","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":540,"y":540,"wires":[["c6c28969.9dbff8","bf2d08a0.229458","c02295c7.804d98","dc76cf88.de8868"]]},{"id":"c6c28969.9dbff8","type":"function","z":"15bc8d4b.90af3b","name":"global target-temp","func":"msg.payload = global.get(\"wz-target\");\nmsg.topic = 'target_temperature';\nnode.status({text:msg.payload + \"°C\"});\nreturn msg;","outputs":1,"noerr":0,"x":770,"y":440,"wires":[["63e04c4d.bb5f6c"]]},{"id":"ae55f012.dbc9e","type":"ui_ui_control","z":"15bc8d4b.90af3b","name":"ui change","x":360,"y":540,"wires":[["bcc9c2f.902a54"]]},{"id":"c02295c7.804d98","type":"function","z":"15bc8d4b.90af3b","name":"global color-state","func":"msg.payload = global.get(\"wz-state\");\nmsg.topic = \"hvac_state\";\nnode.status({text:msg.payload});\nreturn msg;","outputs":1,"noerr":0,"x":770,"y":560,"wires":[["63e04c4d.bb5f6c"]]},{"id":"8872a148.d992","type":"function","z":"15bc8d4b.90af3b","name":"Leaf","func":"minleaf = 15;\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\";\nglobal.set(\"wz-leaf\",msg.payload);\nnode.status({text:msg.payload});\nreturn msg;","outputs":1,"noerr":0,"x":810,"y":720,"wires":[["63e04c4d.bb5f6c"]]},{"id":"4737da1d.f282fc","type":"mqtt in","z":"15bc8d4b.90af3b","name":"","topic":"homeland/haushalt/heizung/Wohnzimmer_Thermostat/desiredTemperature","qos":"2","broker":"34eb4531.5459e2","x":350,"y":720,"wires":[["137bccd8.b66d93","8872a148.d992","19428fcb.0c576"]],"outputLabels":["target_temperature"]},{"id":"b09e0a1f.5122c8","type":"mqtt in","z":"15bc8d4b.90af3b","name":"","topic":"homeland/haushalt/heizung/Wohnzimmer_Thermostat/temperature","qos":"2","broker":"34eb4531.5459e2","x":360,"y":300,"wires":[["773154d5.ab0824","53e6b42a.aabbe4"]],"outputLabels":["ambient_temperature"]},{"id":"137bccd8.b66d93","type":"function","z":"15bc8d4b.90af3b","name":"target_temperature","func":"msg.topic = 'target_temperature';\nglobal.set(\"wz-target\",msg.payload);\nnode.status({text:msg.payload + \"°C\"});\nreturn msg;","outputs":1,"noerr":0,"x":770,"y":680,"wires":[["63e04c4d.bb5f6c","c0dc329a.4f59b"]]},{"id":"7f9ed5e0.953424","type":"mqtt out","z":"15bc8d4b.90af3b","name":"","topic":"homeland/haushalt/heizung/Wohnzimmer_Thermostat/desiredTemperature/set","qos":"2","retain":"false","broker":"34eb4531.5459e2","x":2040,"y":620,"wires":[]},{"id":"bf2d08a0.229458","type":"function","z":"15bc8d4b.90af3b","name":"global target-temp","func":"msg.payload = global.get(\"wz-ambient\");\nmsg.topic = 'ambient_temperature';\nnode.status({text:msg.payload + \"°C\"});\nreturn msg;","outputs":1,"noerr":0,"x":770,"y":500,"wires":[["63e04c4d.bb5f6c"]]},{"id":"dc76cf88.de8868","type":"function","z":"15bc8d4b.90af3b","name":"global target-temp","func":"msg.payload = global.get(\"wz-leaf\");\nmsg.topic = 'has_leaf';\nnode.status({text:msg.payload});\nreturn msg;","outputs":1,"noerr":0,"x":770,"y":620,"wires":[["63e04c4d.bb5f6c"]]},{"id":"a84aee83.b857b","type":"mqtt in","z":"15bc8d4b.90af3b","name":"","topic":"homeland/haushalt/heizung/Wohnzimmer_Thermostat/humidity","qos":"2","broker":"34eb4531.5459e2","x":350,"y":120,"wires":[["6ea3b9b3.85c2e"]],"outputLabels":["humidity"]},{"id":"b04d3a0d.8c6ba8","type":"trigger","z":"15bc8d4b.90af3b","op1":"","op2":"","op1type":"nul","op2type":"payl","duration":"4","extend":true,"units":"s","reset":"","bytopic":"all","name":"","x":1680,"y":620,"wires":[["7f9ed5e0.953424"]]},{"id":"8b86a571.ef3fb","type":"mqtt in","z":"15bc8d4b.90af3b","name":"","topic":"homeland/haushalt/heizung/Wohnzimmer_Thermostat/voc","qos":"2","broker":"34eb4531.5459e2","x":330,"y":180,"wires":[["4b4c2777.fcd59"]]},{"id":"4b4c2777.fcd59","type":"function","z":"15bc8d4b.90af3b","name":"","func":"node.status({text:msg.payload});\nbad = 'Air: <div class=\"fa fa-thumbs-down fa-2x nr-dashboard-error\"></div>';\nok = 'Air: <div class=\"fa fa-thumbs-up fa-2x nr-dashboard-warning\"></div>';\ngood = 'Air: <div class=\"fa fa-thumbs-up fa-2x nr-dashboard-ok\"></div>';\nif (msg.payload > 1750) { \n    msg.payload = (bad);\n    msg.topic = 'air';\n}\nif (msg.payload <= 1750 && msg.payload > 750) {\n    msg.payload = (ok);\n    msg.topic = 'air';\n}\nif (msg.payload <= 750) {\n    msg.payload = (good);\n    msg.topic = 'air';\n}\nreturn msg;","outputs":1,"noerr":0,"x":670,"y":180,"wires":[["44ad4257.3b5dec"]]},{"id":"45291976.3021f","type":"mqtt in","z":"15bc8d4b.90af3b","name":"","topic":"homeland/haushalt/heizung/Wohnzimmer_Thermostat/windowOpen","qos":"2","broker":"34eb4531.5459e2","x":360,"y":240,"wires":[["3708d80.98e8aa8"]]},{"id":"3708d80.98e8aa8","type":"function","z":"15bc8d4b.90af3b","name":"","func":"node.status({text:msg.payload});\ntext = '<div class=\"nr-dashboard-warning\">Window</div>';\nif (msg.payload == \"1\") {\n    msg.payload = (text);\n    msg.topic = 'window';\n}\nreturn msg;","outputs":1,"noerr":0,"x":670,"y":240,"wires":[["44ad4257.3b5dec"]]},{"id":"f6640250.4d957","type":"ui_text","z":"15bc8d4b.90af3b","group":"d2bea201.d68888","order":1,"width":"0","height":"0","name":"Temperatur/Luftfeuchtigkeit","label":"{{msg.payload.humidity.payload}}","format":"{{msg.payload.air.payload}} {{msg.payload.window.payload}}","layout":"row-spread","x":1040,"y":180,"wires":[]},{"id":"6ea3b9b3.85c2e","type":"function","z":"15bc8d4b.90af3b","name":"","func":"node.status({text:msg.payload});\ntext1 = '<div class=\"fa fa-tint fa-2x nr-dashboard-dim\"></div> ';\ntext2 = ' %';\nmsg.payload = (text1 + msg.payload + text2);\nmsg.topic = 'humidity';\nreturn msg;","outputs":1,"noerr":0,"x":670,"y":120,"wires":[["44ad4257.3b5dec"]]},{"id":"44ad4257.3b5dec","type":"join","z":"15bc8d4b.90af3b","name":"","mode":"custom","build":"object","property":"","propertyType":"full","key":"topic","joiner":"\\n","joinerType":"str","accumulate":true,"timeout":"","count":"1","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":830,"y":180,"wires":[["f6640250.4d957"]]},{"id":"2ab0e1e3.ac211e","type":"function","z":"15bc8d4b.90af3b","name":"Nur bei Abweichung senden","func":"if (msg.topic === 'target_temperature_old')\n{\n  oldvalue = msg.payload;\n} \nelse if (msg.topic === 'target_temperature')\n{\n  newvalue = msg.payload;\n} \n\nif (oldvalue != newvalue) {\n  oldvalue = newvalue;\n  return {payload: newvalue};\n}","outputs":1,"noerr":0,"x":1460,"y":620,"wires":[["b04d3a0d.8c6ba8"]]},{"id":"19428fcb.0c576","type":"function","z":"15bc8d4b.90af3b","name":"target_temperature_old","func":"msg.topic = 'target_temperature_old';\nnode.status({text:msg.payload + \"°C\"});\nreturn msg;","outputs":1,"noerr":0,"x":790,"y":760,"wires":[["2ab0e1e3.ac211e"]]},{"id":"d2bea201.d68888","type":"ui_group","z":"","name":"Wohnzimmer","tab":"7a08a4e0.9f9cf4","disp":true,"width":"6","collapse":false},{"id":"34eb4531.5459e2","type":"mqtt-broker","z":"","name":"","broker":"192.168.0.8","port":"1883","clientid":"","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"willTopic":"homeland/haushalt/steuerung/nodered/$online","willQos":"0","willPayload":"false","birthTopic":"","birthQos":"0","birthPayload":""},{"id":"7a08a4e0.9f9cf4","type":"ui_tab","z":"","name":"Klima","icon":"dashboard"}]
« Letzte Änderung: 13 April 2018, 11:30:09 von Master_Nick »
RasPi 3 mit nanoCUL (a-culfw) | IT(V1&V3), IT-PIR, THGR122NX |Co² | alexa-fhem 0.3.0 | WOL | NFC | Harmony UltimateHub | Homestate | Roomba | 10" Touch mit Node-Red | SonOff S20
Gefällt mir Gefällt mir x 3 Liste anzeigen

Offline Master_Nick

  • Sr. Member
  • ****
  • Beiträge: 550
Antw:Node-Red als Frontend
« Antwort #65 am: 16 April 2018, 19:34:34 »
So ich habe noch weiter getweakt.

Wenn ihr Fragen oder Code braucht einfach Bescheid geben :-)
Sonst belasse ich es erst mal beim komplexesten dem Wohnzimmerthermostat.
Generell habe ich alle angepasst um den Minimal Wert und den Maximalwert zu ändern (statt 10° 4° beim Balkon -20° bis 50°).
Und Missbrauche die nun der Optik wegen auch für normales Thermo/Hygrometer. Auch habe ich das symbolisieren von Heizen von Zieltemp >= Sensortemp auf nur > geändert.

Ansonsten mal ein Bild von meinem Touch Display - befeuert von einem Raspi3.

[{"id":"53e6b42a.aabbe4","type":"function","z":"15bc8d4b.90af3b","name":"Konvertieren der Temperatur","func":"msg.payload = parseFloat(msg.payload);\nmsg.topic = 'sensor_temperature';\nnode.status({text:msg.payload + \"°C\"});\nreturn msg;","outputs":1,"noerr":0,"x":810,"y":300,"wires":[["730bb5a.9f6bfcc","c0dc329a.4f59b"]]},{"id":"730bb5a.9f6bfcc","type":"debug","z":"15bc8d4b.90af3b","name":"Temp Sensor","active":false,"console":"false","complete":"payload","x":1130,"y":300,"wires":[]},{"id":"63e04c4d.bb5f6c","type":"ui_template","z":"15bc8d4b.90af3b","group":"d2bea201.d68888","name":"Nest","order":5,"width":"6","height":"6","format":"<div id=\"thermostat-WZ\"></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 || 4, // 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-WZ'),{\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":1030,"y":540,"wires":[["228ed0cb.d3b908"]]},{"id":"773154d5.ab0824","type":"function","z":"15bc8d4b.90af3b","name":"ambient_temperature","func":"msg.topic = \"ambient_temperature\";\nglobal.set(\"wz-ambient\",msg.payload);\nnode.status({text:msg.payload + \"°C\"});\nreturn msg;","outputs":1,"noerr":0,"x":777.6190490722656,"y":388.33333587646484,"wires":[["63e04c4d.bb5f6c"]]},{"id":"1ae634e.7e416cb","type":"function","z":"15bc8d4b.90af3b","name":"hvac_state","func":"global.set(\"wz-state\",msg.payload);\n\nmsg.topic = \"hvac_state\";\nnode.status({text:msg.payload});\nreturn msg;","outputs":1,"noerr":0,"x":1670,"y":340,"wires":[["63e04c4d.bb5f6c"]]},{"id":"228ed0cb.d3b908","type":"function","z":"15bc8d4b.90af3b","name":"target_temperature","func":"if (msg.topic == \"target_temperature\") {\nglobal.set(\"wz-target\",msg.payload);\nreturn msg;\n}","outputs":1,"noerr":0,"x":1210,"y":540,"wires":[["c0dc329a.4f59b","2ab0e1e3.ac211e"]]},{"id":"c0dc329a.4f59b","type":"function","z":"15bc8d4b.90af3b","name":"Temperaturen Vergleichen","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}\nnode.status({text:msg.payload});","outputs":1,"noerr":0,"x":1460,"y":500,"wires":[["7dc8f30f.3d7064"]]},{"id":"7dc8f30f.3d7064","type":"function","z":"15bc8d4b.90af3b","name":"Farbstatus Nest","func":"msg.topic = \"hvac_state\";\nnode.status({text:msg.payload});\nreturn msg;","outputs":1,"noerr":0,"x":1700,"y":500,"wires":[["74d1ff15.059858"]]},{"id":"74d1ff15.059858","type":"switch","z":"15bc8d4b.90af3b","name":"","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"0","vt":"str"},{"t":"eq","v":"1","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":1310,"y":360,"wires":[["3d83fd16.3def3a"],["42df8b17.773ac4"]]},{"id":"3d83fd16.3def3a","type":"template","z":"15bc8d4b.90af3b","name":"Heizen OFF","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"off","output":"str","x":1490,"y":300,"wires":[["1ae634e.7e416cb"]]},{"id":"42df8b17.773ac4","type":"template","z":"15bc8d4b.90af3b","name":"Heizen ON","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"heating","output":"str","x":1490,"y":380,"wires":[["1ae634e.7e416cb"]]},{"id":"bcc9c2f.902a54","type":"delay","z":"15bc8d4b.90af3b","name":"","pauseType":"delay","timeout":"50","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":540,"y":540,"wires":[["c6c28969.9dbff8","bf2d08a0.229458","c02295c7.804d98","dc76cf88.de8868"]]},{"id":"c6c28969.9dbff8","type":"function","z":"15bc8d4b.90af3b","name":"global target-temp","func":"msg.payload = global.get(\"wz-target\");\nmsg.topic = 'target_temperature';\nnode.status({text:msg.payload + \"°C\"});\nreturn msg;","outputs":1,"noerr":0,"x":770,"y":440,"wires":[["63e04c4d.bb5f6c"]]},{"id":"ae55f012.dbc9e","type":"ui_ui_control","z":"15bc8d4b.90af3b","name":"ui change","x":360,"y":540,"wires":[["bcc9c2f.902a54"]]},{"id":"c02295c7.804d98","type":"function","z":"15bc8d4b.90af3b","name":"global color-state","func":"msg.payload = global.get(\"wz-state\");\nmsg.topic = \"hvac_state\";\nnode.status({text:msg.payload});\nreturn msg;","outputs":1,"noerr":0,"x":770,"y":560,"wires":[["63e04c4d.bb5f6c"]]},{"id":"8872a148.d992","type":"function","z":"15bc8d4b.90af3b","name":"Leaf","func":"minleaf = 15;\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\";\nglobal.set(\"wz-leaf\",msg.payload);\nnode.status({text:msg.payload});\nreturn msg;","outputs":1,"noerr":0,"x":810,"y":720,"wires":[["63e04c4d.bb5f6c"]]},{"id":"4737da1d.f282fc","type":"mqtt in","z":"15bc8d4b.90af3b","name":"","topic":"homeland/haushalt/heizung/Wohnzimmer_Thermostat/desiredTemperature","qos":"2","broker":"34eb4531.5459e2","x":350,"y":720,"wires":[["137bccd8.b66d93","8872a148.d992","19428fcb.0c576"]],"outputLabels":["target_temperature"]},{"id":"b09e0a1f.5122c8","type":"mqtt in","z":"15bc8d4b.90af3b","name":"","topic":"homeland/haushalt/heizung/Wohnzimmer_Thermostat/temperature","qos":"2","broker":"34eb4531.5459e2","x":360,"y":300,"wires":[["773154d5.ab0824","53e6b42a.aabbe4"]],"outputLabels":["ambient_temperature"]},{"id":"137bccd8.b66d93","type":"function","z":"15bc8d4b.90af3b","name":"target_temperature","func":"msg.topic = 'target_temperature';\nglobal.set(\"wz-target\",msg.payload);\nnode.status({text:msg.payload + \"°C\"});\nreturn msg;","outputs":1,"noerr":0,"x":770,"y":680,"wires":[["63e04c4d.bb5f6c","c0dc329a.4f59b"]]},{"id":"7f9ed5e0.953424","type":"mqtt out","z":"15bc8d4b.90af3b","name":"","topic":"homeland/haushalt/heizung/Wohnzimmer_Thermostat/desiredTemperature/set","qos":"2","retain":"false","broker":"34eb4531.5459e2","x":2040,"y":620,"wires":[]},{"id":"bf2d08a0.229458","type":"function","z":"15bc8d4b.90af3b","name":"global target-temp","func":"msg.payload = global.get(\"wz-ambient\");\nmsg.topic = 'ambient_temperature';\nnode.status({text:msg.payload + \"°C\"});\nreturn msg;","outputs":1,"noerr":0,"x":770,"y":500,"wires":[["63e04c4d.bb5f6c"]]},{"id":"dc76cf88.de8868","type":"function","z":"15bc8d4b.90af3b","name":"global target-temp","func":"msg.payload = global.get(\"wz-leaf\");\nmsg.topic = 'has_leaf';\nnode.status({text:msg.payload});\nreturn msg;","outputs":1,"noerr":0,"x":770,"y":620,"wires":[["63e04c4d.bb5f6c"]]},{"id":"a84aee83.b857b","type":"mqtt in","z":"15bc8d4b.90af3b","name":"","topic":"homeland/haushalt/heizung/Wohnzimmer_Thermostat/humidity","qos":"2","broker":"34eb4531.5459e2","x":350,"y":120,"wires":[["6ea3b9b3.85c2e"]],"outputLabels":["humidity"]},{"id":"b04d3a0d.8c6ba8","type":"trigger","z":"15bc8d4b.90af3b","op1":"","op2":"","op1type":"nul","op2type":"payl","duration":"4","extend":true,"units":"s","reset":"","bytopic":"all","name":"","x":1680,"y":620,"wires":[["7f9ed5e0.953424"]]},{"id":"8b86a571.ef3fb","type":"mqtt in","z":"15bc8d4b.90af3b","name":"","topic":"homeland/haushalt/heizung/Wohnzimmer_Thermostat/voc","qos":"2","broker":"34eb4531.5459e2","x":330,"y":180,"wires":[["4b4c2777.fcd59"]]},{"id":"4b4c2777.fcd59","type":"function","z":"15bc8d4b.90af3b","name":"","func":"node.status({text:msg.payload});\nbad = 'Air: <div class=\"fa fa-thumbs-down fa-2x nr-dashboard-error\"></div>';\nok = 'Air: <div class=\"fa fa-thumbs-up fa-2x nr-dashboard-warning\"></div>';\ngood = 'Air: <div class=\"fa fa-thumbs-up fa-2x nr-dashboard-ok\"></div>';\nif (msg.payload > 1750) { \n    msg.payload = (bad);\n    msg.topic = 'air';\n}\nif (msg.payload <= 1750 && msg.payload > 750) {\n    msg.payload = (ok);\n    msg.topic = 'air';\n}\nif (msg.payload <= 750) {\n    msg.payload = (good);\n    msg.topic = 'air';\n}\nreturn msg;","outputs":1,"noerr":0,"x":670,"y":180,"wires":[["44ad4257.3b5dec"]]},{"id":"45291976.3021f","type":"mqtt in","z":"15bc8d4b.90af3b","name":"","topic":"homeland/haushalt/heizung/Wohnzimmer_Thermostat/windowOpen","qos":"2","broker":"34eb4531.5459e2","x":360,"y":240,"wires":[["3708d80.98e8aa8"]]},{"id":"3708d80.98e8aa8","type":"function","z":"15bc8d4b.90af3b","name":"","func":"node.status({text:msg.payload});\ntext = '<div class=\"nr-dashboard-warning\">Window</div>';\nif (msg.payload == \"1\") {\n    msg.payload = (text);\n    msg.topic = 'window';\n}\nreturn msg;","outputs":1,"noerr":0,"x":670,"y":240,"wires":[["44ad4257.3b5dec"]]},{"id":"f6640250.4d957","type":"ui_text","z":"15bc8d4b.90af3b","group":"d2bea201.d68888","order":1,"width":"0","height":"0","name":"Temperatur/Luftfeuchtigkeit","label":"{{msg.payload.humidity.payload}}","format":"{{msg.payload.air.payload}} {{msg.payload.window.payload}}","layout":"row-spread","x":1040,"y":180,"wires":[]},{"id":"6ea3b9b3.85c2e","type":"function","z":"15bc8d4b.90af3b","name":"","func":"node.status({text:msg.payload});\ntext1 = '<div class=\"fa fa-tint fa-2x nr-dashboard-dim\"></div> ';\ntext2 = ' %';\nmsg.payload = (text1 + msg.payload + text2);\nmsg.topic = 'humidity';\nreturn msg;","outputs":1,"noerr":0,"x":670,"y":120,"wires":[["44ad4257.3b5dec"]]},{"id":"44ad4257.3b5dec","type":"join","z":"15bc8d4b.90af3b","name":"","mode":"custom","build":"object","property":"","propertyType":"full","key":"topic","joiner":"\\n","joinerType":"str","accumulate":true,"timeout":"","count":"1","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":830,"y":180,"wires":[["f6640250.4d957"]]},{"id":"2ab0e1e3.ac211e","type":"function","z":"15bc8d4b.90af3b","name":"Nur bei Abweichung senden","func":"if (msg.topic === 'target_temperature_old')\n{\n  oldvalue = msg.payload;\n} \nelse if (msg.topic === 'target_temperature')\n{\n  newvalue = msg.payload;\n} \n\nif (oldvalue != newvalue) {\n  oldvalue = newvalue;\n  return {payload: newvalue};\n}","outputs":1,"noerr":0,"x":1460,"y":620,"wires":[["b04d3a0d.8c6ba8"]]},{"id":"19428fcb.0c576","type":"function","z":"15bc8d4b.90af3b","name":"target_temperature_old","func":"msg.topic = 'target_temperature_old';\nnode.status({text:msg.payload + \"°C\"});\nreturn msg;","outputs":1,"noerr":0,"x":790,"y":760,"wires":[["2ab0e1e3.ac211e"]]},{"id":"d2bea201.d68888","type":"ui_group","z":"","name":"Wohnzimmer","tab":"7a08a4e0.9f9cf4","order":1,"disp":true,"width":"7","collapse":false},{"id":"34eb4531.5459e2","type":"mqtt-broker","z":"","name":"","broker":"192.168.0.8","port":"1883","clientid":"","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"willTopic":"homeland/haushalt/steuerung/nodered/$online","willQos":"0","willPayload":"false","birthTopic":"","birthQos":"0","birthPayload":""},{"id":"7a08a4e0.9f9cf4","type":"ui_tab","z":"","name":"Klima","icon":"dashboard","order":1}]
« Letzte Änderung: 16 April 2018, 19:40:10 von Master_Nick »
RasPi 3 mit nanoCUL (a-culfw) | IT(V1&V3), IT-PIR, THGR122NX |Co² | alexa-fhem 0.3.0 | WOL | NFC | Harmony UltimateHub | Homestate | Roomba | 10" Touch mit Node-Red | SonOff S20
Gefällt mir Gefällt mir x 1 Liste anzeigen

Offline SamNitro

  • Full Member
  • ***
  • Beiträge: 442
  • Kölner Wimpelbeauftragter
Antw:Node-Red als Frontend
« Antwort #66 am: 16 Mai 2018, 19:34:20 »
Hat jemand eine schöne Lösung einen WeekdayTimer zu realisieren, wie beim TabletUI?
https://forum.fhem.de/index.php/topic,48106.msg397622.html#msg397622
Gruß Patrick
------------------------
(Rpi 3, Stretch) (HM-LGW) (CUL868) (CUL433) (Homematic Komponenten) (FS20) (SONOFF) (ESP8266) (EchoDot)

Offline Master_Nick

  • Sr. Member
  • ****
  • Beiträge: 550
Antw:Node-Red als Frontend
« Antwort #67 am: 16 Mai 2018, 20:05:48 »
 ;) Alos ich nicht, da ich dafür aber auch irgendwie keinerlei Verwendung habe.
Jede tiefere Schaltung die nicht an aus ist frühstücke ich in FHEM ab.
Machbar sind diverse Logiken aber generell auch in Node-Red.
RasPi 3 mit nanoCUL (a-culfw) | IT(V1&V3), IT-PIR, THGR122NX |Co² | alexa-fhem 0.3.0 | WOL | NFC | Harmony UltimateHub | Homestate | Roomba | 10" Touch mit Node-Red | SonOff S20

Offline SamNitro

  • Full Member
  • ***
  • Beiträge: 442
  • Kölner Wimpelbeauftragter
Antw:Node-Red als Frontend
« Antwort #68 am: 16 Mai 2018, 20:25:44 »
Ja ein paar Lösungen habe ich auch schon aber leider nicht so komfortabel wie der WeekdayTimer.

node-red-contrib-schedex:
Schöne Funktion mit sunrise sunset.

node-red-contrib-simple-weekly-scheduler:
Einfache Schaltungen.

Aber beide haben eine kleines Problem die wollen immer einen aus und an Befehl haben. Wenn man nur eins haben will muss man filtern.
Gruß Patrick
------------------------
(Rpi 3, Stretch) (HM-LGW) (CUL868) (CUL433) (Homematic Komponenten) (FS20) (SONOFF) (ESP8266) (EchoDot)

Offline Master_Nick

  • Sr. Member
  • ****
  • Beiträge: 550
Antw:Node-Red als Frontend
« Antwort #69 am: Gestern um 12:17:10 »
Das kannste dir doch bestimmt mit einem Switch auf JS Basis umbauen.
Oder was meinst du mit AN/AUS Befehl?
RasPi 3 mit nanoCUL (a-culfw) | IT(V1&V3), IT-PIR, THGR122NX |Co² | alexa-fhem 0.3.0 | WOL | NFC | Harmony UltimateHub | Homestate | Roomba | 10" Touch mit Node-Red | SonOff S20

Offline SamNitro

  • Full Member
  • ***
  • Beiträge: 442
  • Kölner Wimpelbeauftragter
Antw:Node-Red als Frontend
« Antwort #70 am: Gestern um 12:25:13 »
Die fertigen Nodes sind so ausgelegt das man eine einschaltzeit und eine ausschaltzeit programmieren muss.

Wenn ich jetzt aber z.B. einen Rollladen nur um 09:00 Uhr hochfahren möchte muss ich die andere Zeit filtern.

Schön wäre hier ein Node wo man pro Position die man hinzufügt nur eine schalt Befehl hat.

Als Eingabe dann
-Zeit
-On/Off oder Position
-ggf. noch der Wochentag


Mobil unterwegs!
« Letzte Änderung: Gestern um 12:29:23 von SamNitro »
Gruß Patrick
------------------------
(Rpi 3, Stretch) (HM-LGW) (CUL868) (CUL433) (Homematic Komponenten) (FS20) (SONOFF) (ESP8266) (EchoDot)

 

decade-submarginal