Node-Red als Frontend

Begonnen von Master_Nick, 26 Oktober 2017, 13:07:28

Vorheriges Thema - Nächstes Thema

Shojo

Zitat von: Shojo am 28 Februar 2018, 15:22:33
Das ist HTML (http://www.ajso.lt/node-red/nest-thermostat-dashboard-html5-widget-node-red/)

Hmm....
Aber so richtig Funktioniert das mit den Steuerelement nicht.
Bei einen Page reload fehlen ab und dann die Werte :(

FHEM auf: Shuttle PC (x64) (Docker)
Bridge: SignalESP 433mHz, ConBee (deCONZ in Docker)
Rest: ESP8266, SONOFF, Sonos, Echo Dot, Xiaomi Vacuum (root), ESP RGBWW Wifi Led Controller, Node-RED, LEDMatrix, Pixel It

Master_Nick

Das kann durchaus mal am Browser liegen - ich hatte sowas mal bei Firefox und FHEM.... da blieb dann ne ganze Ecke einfach hängen und fehlte und alles war unnutzbar bis man oben in die Adresszeile klickte und Enter drückte. (STRG+SHIFT+R brachte nicht das gleiche Ergebnis)
Rancher K8s Cluster mit nanoCUL (a-culfw) | IObroker | IT(V1&V3), IT-PIR, THGR122NX |Co² | alexa-fhem | WOL | NFC | Harmony UltimateHub | Anwesenheitserkennnung | Roomba | 10" Touch mit Node-Red | SonOff S20 | SonOff Touch | SonOff Dual | Rolladen | Und ganz viel anderes tolles Gerödel.... ;-)

Shojo

Habe schon mehre Browser und Devices durch kommt auf allen leider vor :(
FHEM auf: Shuttle PC (x64) (Docker)
Bridge: SignalESP 433mHz, ConBee (deCONZ in Docker)
Rest: ESP8266, SONOFF, Sonos, Echo Dot, Xiaomi Vacuum (root), ESP RGBWW Wifi Led Controller, Node-RED, LEDMatrix, Pixel It

Master_Nick

Dann ist das eher raus... :-/ Schade
Oder mal Bescheid sagen bei dem Typen :-D Bug öffnen.
Rancher K8s Cluster mit nanoCUL (a-culfw) | IObroker | IT(V1&V3), IT-PIR, THGR122NX |Co² | alexa-fhem | WOL | NFC | Harmony UltimateHub | Anwesenheitserkennnung | Roomba | 10" Touch mit Node-Red | SonOff S20 | SonOff Touch | SonOff Dual | Rolladen | Und ganz viel anderes tolles Gerödel.... ;-)

SpeedyRS2

Ich bin mittlerweile auch bei NodeRed als Frontend für FHEM angekommen.
Ich habe vorher unheimlich viel mit dem TabletUI rumprobiert und es immer wieder umgestellt. Optisch hat es mir nachher sogar sehr gut gefallen. Allerdings fand ich die Bedienung über das TabletUI irgendwie doch sehr träge und hakelig. Das hat mich echt gestört. Da bringt das schickste UI nichts.

Daraufhin habe ich mich mal an NodeRed gesetzt.
Ich war von der ersten Sekunde an total begeistert.
Kommunikation zwischen FHEM und NodeRed läuft über MQTT und das ohne Zeitverlust. Ich hatte schon die Befürchtung, dass man die zusätzliche Schicht merkt. Ist aber gar nicht so. Änderungen in FHEM werden direkt in NodeRed dargestellt.
Das Dashboard ist super simpel zu erstellen und wirklich flott in der Bedienung. Gerade die Slider haben mich beim TabletUI in den Wahnsinn getrieben. Im NodeRed Dashboard flutscht es einfach nur perfekt.

Hier mal ein paar Screens, wie Teile meines Dashboards jetzt aussehen:

SpeedyRS2

Für das NEST Widget hab ich, glaube ich  ;) , auch eine Lösung gefunden.

Änderungen habe ich an den im Screenshot markierten Elementen vorgenommen.

Hier mal der exportierte Flow:
[{"id":"2c174318.8fafec","type":"tab","label":"Flow 2","disabled":false,"info":""},{"id":"85e9547.e3387a8","type":"ui_template","z":"2c174318.8fafec","group":"8db78a1d.f45e08","name":"Nest","order":1,"width":"6","height":"6","format":"<div id=\"thermostat\"></div>\n\n<style>\n@import url(http://fonts.googleapis.com/css?family=Open+Sans:300);\n#thermostat {\n  margin: 0 auto;\n  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\n.dial {\n  -webkit-user-select: none;\n     -moz-user-select: none;\n      -ms-user-select: none;\n          user-select: none;\n}\n.dial.away .dial__ico__leaf {\n  visibility: hidden;\n}\n.dial.away .dial__lbl--target {\n  visibility: hidden;\n}\n.dial.away .dial__lbl--target--half {\n  visibility: hidden;\n}\n.dial.away .dial__lbl--away {\n  opacity: 1;\n}\n.dial .dial__shape {\n  -webkit-transition: fill 0.5s;\n  transition: fill 0.5s;\n}\n.dial__ico__leaf {\n  fill: #13EB13;\n  opacity: 0;\n  -webkit-transition: opacity 0.5s;\n  transition: opacity 0.5s;\n  pointer-events: none;\n}\n.dial.has-leaf .dial__ico__leaf {\n  display: block;\n  opacity: 1;\n  pointer-events: initial;\n}\n.dial__editableIndicator {\n  fill: white;\n  fill-rule: evenodd;\n  opacity: 0;\n  -webkit-transition: opacity 0.5s;\n  transition: opacity 0.5s;\n}\n.dial--edit .dial__editableIndicator {\n  opacity: 1;\n}\n.dial--state--off .dial__shape {\n  fill: #3d3c3c;\n}\n.dial--state--heating .dial__shape {\n  fill: #E36304;\n}\n.dial--state--cooling .dial__shape {\n  fill: #007AF1;\n}\n.dial__ticks path {\n  fill: rgba(255, 255, 255, 0.3);\n}\n.dial__ticks path.active {\n  fill: rgba(255, 255, 255, 0.8);\n}\n.dial text {\n  fill: white;\n  text-anchor: middle;\n  font-family: Helvetica, sans-serif;\n  alignment-baseline: central;\n}\n.dial__lbl--target {\n  font-size: 120px;\n  font-weight: bold;\n}\n.dial__lbl--target--half {\n  font-size: 40px;\n  font-weight: bold;\n  opacity: 0;\n  -webkit-transition: opacity 0.1s;\n  transition: opacity 0.1s;\n}\n.dial__lbl--target--half.shown {\n  opacity: 1;\n  -webkit-transition: opacity 0s;\n  transition: opacity 0s;\n}\n.dial__lbl--ambient {\n  font-size: 22px;\n  font-weight: bold;\n}\n.dial__lbl--away {\n  font-size: 72px;\n  font-weight: bold;\n  opacity: 0;\n  pointer-events: none;\n}\n#controls {\n  font-family: Open Sans;\n  background-color: rgba(255, 255, 255, 0.25);\n  padding: 20px;\n  border-radius: 5px;\n  position: absolute;\n  left: 50%;\n  -webkit-transform: translatex(-50%);\n          transform: translatex(-50%);\n  margin-top: 20px;\n}\n#controls label {\n  text-align: left;\n  display: block;\n}\n#controls label span {\n  display: inline-block;\n  width: 200px;\n  text-align: right;\n  font-size: 0.8em;\n  text-transform: uppercase;\n}\n#controls p {\n  margin: 0;\n  margin-bottom: 1em;\n  padding-bottom: 1em;\n  border-bottom: 2px solid #ccc;\n}\n</style>\n<script>\n    var thermostatDial = (function() {\n\t\n\t/*\n\t * Utility functions\n\t */\n\t\n\t// Create an element with proper SVG namespace, optionally setting its attributes and appending it to another element\n\tfunction createSVGElement(tag,attributes,appendTo) {\n\t\tvar element = document.createElementNS('http://www.w3.org/2000/svg',tag);\n\t\tattr(element,attributes);\n\t\tif (appendTo) {\n\t\t\tappendTo.appendChild(element);\n\t\t}\n\t\treturn element;\n\t}\n\t\n\t// Set attributes for an element\n\tfunction attr(element,attrs) {\n\t\tfor (var i in attrs) {\n\t\t\telement.setAttribute(i,attrs[i]);\n\t\t}\n\t}\n\t\n\t// Rotate a cartesian point about given origin by X degrees\n\tfunction rotatePoint(point, angle, origin) {\n\t\tvar radians = angle * Math.PI/180;\n\t\tvar x = point[0]-origin[0];\n\t\tvar y = point[1]-origin[1];\n\t\tvar x1 = x*Math.cos(radians) - y*Math.sin(radians) + origin[0];\n\t\tvar y1 = x*Math.sin(radians) + y*Math.cos(radians) + origin[1];\n\t\treturn [x1,y1];\n\t}\n\t\n\t// Rotate an array of cartesian points about a given origin by X degrees\n\tfunction rotatePoints(points, angle, origin) {\n\t\treturn points.map(function(point) {\n\t\t\treturn rotatePoint(point, angle, origin);\n\t\t});\n\t}\n\t\n\t// Given an array of points, return an SVG path string representing the shape they define\n\tfunction pointsToPath(points) {\n\t\treturn points.map(function(point, iPoint) {\n\t\t\treturn (iPoint>0?'L':'M') + point[0] + ' ' + point[1];\n\t\t}).join(' ')+'Z';\n\t}\n\t\n\tfunction circleToPath(cx, cy, r) {\n\t\treturn [\n\t\t\t\"M\",cx,\",\",cy,\n\t\t\t\"m\",0-r,\",\",0,\n\t\t\t\"a\",r,\",\",r,0,1,\",\",0,r*2,\",\",0,\n\t\t\t\"a\",r,\",\",r,0,1,\",\",0,0-r*2,\",\",0,\n\t\t\t\"z\"\n\t\t].join(' ').replace(/\\s,\\s/g,\",\");\n\t}\n\t\n\tfunction donutPath(cx,cy,rOuter,rInner) {\n\t\treturn circleToPath(cx,cy,rOuter) + \" \" + circleToPath(cx,cy,rInner);\n\t}\n\t\n\t// Restrict a number to a min + max range\n\tfunction restrictToRange(val,min,max) {\n\t\tif (val < min) return min;\n\t\tif (val > max) return max;\n\t\treturn val;\n\t}\n\t\n\t// Round a number to the nearest 0.5\n\tfunction roundHalf(num) {\n\t\treturn Math.round(num*2)/2;\n\t}\n\t\n\tfunction setClass(el, className, state) {\n\t\tel.classList[state ? 'add' : 'remove'](className);\n\t}\n\t\n\t/*\n\t * The \"MEAT\"\n\t */\n\n\treturn function(targetElement, options) {\n\t\tvar self = this;\n\t\t\n\t\t/*\n\t\t * Options\n\t\t */\n\t\toptions = options || {};\n\t\toptions = {\n\t\t\tdiameter: options.diameter || 400,\n\t\t\tminValue: options.minValue || 10, // Minimum value for target temperature\n\t\t\tmaxValue: options.maxValue || 30, // Maximum value for target temperature\n\t\t\tnumTicks: options.numTicks || 200, // Number of tick lines to display around the dial\n\t\t\tonSetTargetTemperature: options.onSetTargetTemperature || function() {}, // Function called when new target temperature set by the dial\n\t\t};\n\t\t\n\t\t/*\n\t\t * Properties - calculated from options in many cases\n\t\t */\n\t\tvar properties = {\n\t\t\ttickDegrees: 300, //  Degrees of the dial that should be covered in tick lines\n\t\t\trangeValue: options.maxValue - options.minValue,\n\t\t\tradius: options.diameter/2,\n\t\t\tticksOuterRadius: options.diameter / 30,\n\t\t\tticksInnerRadius: options.diameter / 8,\n\t\t\thvac_states: ['off', 'heating', 'cooling'],\n\t\t\tdragLockAxisDistance: 15,\n\t\t}\n\t\tproperties.lblAmbientPosition = [properties.radius, properties.ticksOuterRadius-(properties.ticksOuterRadius-properties.ticksInnerRadius)/2]\n\t\tproperties.offsetDegrees = 180-(360-properties.tickDegrees)/2;\n\t\t\n\t\t/*\n\t\t * Object state\n\t\t */\n\t\tvar state = {\n\t\t\ttarget_temperature: options.minValue,\n\t\t\tambient_temperature: options.minValue,\n\t\t\thvac_state: properties.hvac_states[0],\n\t\t\thas_leaf: false,\n\t\t\taway: false\n\t\t};\n\t\t\n\t\t/*\n\t\t * Property getter / setters\n\t\t */\n\t\tObject.defineProperty(this,'target_temperature',{\n\t\t\tget: function() {\n\t\t\t\treturn state.target_temperature;\n\t\t\t},\n\t\t\tset: function(val) {\n\t\t\t\tstate.target_temperature = restrictTargetTemperature(+val);\n\t\t\t\trender();\n\t\t\t}\n\t\t});\n\t\tObject.defineProperty(this,'ambient_temperature',{\n\t\t\tget: function() {\n\t\t\t\treturn state.ambient_temperature;\n\t\t\t},\n\t\t\tset: function(val) {\n\t\t\t\tstate.ambient_temperature = roundHalf(+val);\n\t\t\t\trender();\n\t\t\t}\n\t\t});\n\t\tObject.defineProperty(this,'hvac_state',{\n\t\t\tget: function() {\n\t\t\t\treturn state.hvac_state;\n\t\t\t},\n\t\t\tset: function(val) {\n\t\t\t\tif (properties.hvac_states.indexOf(val)>=0) {\n\t\t\t\t\tstate.hvac_state = val;\n\t\t\t\t\trender();\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\tObject.defineProperty(this,'has_leaf',{\n\t\t\tget: function() {\n\t\t\t\treturn state.has_leaf;\n\t\t\t},\n\t\t\tset: function(val) {\n\t\t\t\tstate.has_leaf = !!val;\n\t\t\t\trender();\n\t\t\t}\n\t\t});\n\t\tObject.defineProperty(this,'away',{\n\t\t\tget: function() {\n\t\t\t\treturn state.away;\n\t\t\t},\n\t\t\tset: function(val) {\n\t\t\t\tstate.away = !!val;\n\t\t\t\trender();\n\t\t\t}\n\t\t});\n\t\t\n\t\t/*\n\t\t * SVG\n\t\t */\n\t\tvar svg = createSVGElement('svg',{\n\t\t\twidth: '100%', //options.diameter+'px',\n\t\t\theight: '100%', //options.diameter+'px',\n\t\t\tviewBox: '0 0 '+options.diameter+' '+options.diameter,\n\t\t\tclass: 'dial'\n\t\t},targetElement);\n\t\t// CIRCULAR DIAL\n\t\tvar circle = createSVGElement('circle',{\n\t\t\tcx: properties.radius,\n\t\t\tcy: properties.radius,\n\t\t\tr: properties.radius,\n\t\t\tclass: 'dial__shape'\n\t\t},svg);\n\t\t// EDITABLE INDICATOR\n\t\tvar editCircle = createSVGElement('path',{\n\t\t\td: donutPath(properties.radius,properties.radius,properties.radius-4,properties.radius-8),\n\t\t\tclass: 'dial__editableIndicator',\n\t\t},svg);\n\t\t\n\t\t/*\n\t\t * Ticks\n\t\t */\n\t\tvar ticks = createSVGElement('g',{\n\t\t\tclass: 'dial__ticks'\t\n\t\t},svg);\n\t\tvar tickPoints = [\n\t\t\t[properties.radius-1, properties.ticksOuterRadius],\n\t\t\t[properties.radius+1, properties.ticksOuterRadius],\n\t\t\t[properties.radius+1, properties.ticksInnerRadius],\n\t\t\t[properties.radius-1, properties.ticksInnerRadius]\n\t\t];\n\t\tvar tickPointsLarge = [\n\t\t\t[properties.radius-1.5, properties.ticksOuterRadius],\n\t\t\t[properties.radius+1.5, properties.ticksOuterRadius],\n\t\t\t[properties.radius+1.5, properties.ticksInnerRadius+20],\n\t\t\t[properties.radius-1.5, properties.ticksInnerRadius+20]\n\t\t];\n\t\tvar theta = properties.tickDegrees/options.numTicks;\n\t\tvar tickArray = [];\n\t\tfor (var iTick=0; iTick<options.numTicks; iTick++) {\n\t\t\ttickArray.push(createSVGElement('path',{d:pointsToPath(tickPoints)},ticks));\n\t\t};\n\t\t\n\t\t/*\n\t\t * Labels\n\t\t */\n\t\tvar lblTarget = createSVGElement('text',{\n\t\t\tx: properties.radius,\n\t\t\ty: properties.radius,\n\t\t\tclass: 'dial__lbl dial__lbl--target'\n\t\t},svg);\n\t\tvar lblTarget_text = document.createTextNode('');\n\t\tlblTarget.appendChild(lblTarget_text);\n\t\t//\n\t\tvar lblTargetHalf = createSVGElement('text',{\n\t\t\tx: properties.radius + properties.radius/2.5,\n\t\t\ty: properties.radius - properties.radius/8,\n\t\t\tclass: 'dial__lbl dial__lbl--target--half'\n\t\t},svg);\n\t\tvar lblTargetHalf_text = document.createTextNode('5');\n\t\tlblTargetHalf.appendChild(lblTargetHalf_text);\n\t\t//\n\t\tvar lblAmbient = createSVGElement('text',{\n\t\t\tclass: 'dial__lbl dial__lbl--ambient'\n\t\t},svg);\n\t\tvar lblAmbient_text = document.createTextNode('');\n\t\tlblAmbient.appendChild(lblAmbient_text);\n\t\t//\n\t\tvar lblAway = createSVGElement('text',{\n\t\t\tx: properties.radius,\n\t\t\ty: properties.radius,\n\t\t\tclass: 'dial__lbl dial__lbl--away'\n\t\t},svg);\n\t\tvar lblAway_text = document.createTextNode('AWAY');\n\t\tlblAway.appendChild(lblAway_text);\n\t\t//\n\t\tvar icoLeaf = createSVGElement('path',{\n\t\t\tclass: 'dial__ico__leaf'\n\t\t},svg);\n\t\t\n\t\t/*\n\t\t * LEAF\n\t\t */\n\t\tvar leafScale = properties.radius/5/100;\n\t\tvar leafDef = [\"M\", 3, 84, \"c\", 24, 17, 51, 18, 73, -6, \"C\", 100, 52, 100, 22, 100, 4, \"c\", -13, 15, -37, 9, -70, 19, \"C\", 4, 32, 0, 63, 0, 76, \"c\", 6, -7, 18, -17, 33, -23, 24, -9, 34, -9, 48, -20, -9, 10, -20, 16, -43, 24, \"C\", 22, 63, 8, 78, 3, 84, \"z\"].map(function(x) {\n\t\t\treturn isNaN(x) ? x : x*leafScale;\n\t\t}).join(' ');\n\t\tvar translate = [properties.radius-(leafScale*100*0.5),properties.radius*1.5]\n\t\tvar icoLeaf = createSVGElement('path',{\n\t\t\tclass: 'dial__ico__leaf',\n\t\t\td: leafDef,\n\t\t\ttransform: 'translate('+translate[0]+','+translate[1]+')'\n\t\t},svg);\n\t\t\t\n\t\t/*\n\t\t * RENDER\n\t\t */\n\t\tfunction render() {\n\t\t\trenderAway();\n\t\t\trenderHvacState();\n\t\t\trenderTicks();\n\t\t\trenderTargetTemperature();\n\t\t\trenderAmbientTemperature();\n\t\t\trenderLeaf();\n\t\t}\n\t\trender();\n\n\t\t/*\n\t\t * RENDER - ticks\n\t\t */\n\t\tfunction renderTicks() {\n\t\t\tvar vMin, vMax;\n\t\t\tif (self.away) {\n\t\t\t\tvMin = self.ambient_temperature;\n\t\t\t\tvMax = vMin;\n\t\t\t} else {\n\t\t\t\tvMin = Math.min(self.ambient_temperature, self.target_temperature);\n\t\t\t\tvMax = Math.max(self.ambient_temperature, self.target_temperature);\n\t\t\t}\n\t\t\tvar min = restrictToRange(Math.round((vMin-options.minValue)/properties.rangeValue * options.numTicks),0,options.numTicks-1);\n\t\t\tvar max = restrictToRange(Math.round((vMax-options.minValue)/properties.rangeValue * options.numTicks),0,options.numTicks-1);\n\t\t\t//\n\t\t\ttickArray.forEach(function(tick,iTick) {\n\t\t\t\tvar isLarge = iTick==min || iTick==max;\n\t\t\t\tvar isActive = iTick >= min && iTick <= max;\n\t\t\t\tattr(tick,{\n\t\t\t\t\td: pointsToPath(rotatePoints(isLarge ? tickPointsLarge: tickPoints,iTick*theta-properties.offsetDegrees,[properties.radius, properties.radius])),\n\t\t\t\t\tclass: isActive ? 'active' : ''\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\t\n\t\t/*\n\t\t * RENDER - ambient temperature\n\t\t */\n\t\tfunction renderAmbientTemperature() {\n\t\t\tlblAmbient_text.nodeValue = Math.floor(self.ambient_temperature);\n\t\t\tif (self.ambient_temperature%1!=0) {\n\t\t\t\tlblAmbient_text.nodeValue += '⁵';\n\t\t\t}\n\t\t\tvar peggedValue = restrictToRange(self.ambient_temperature, options.minValue, options.maxValue);\n\t\t\tdegs = properties.tickDegrees * (peggedValue-options.minValue)/properties.rangeValue - properties.offsetDegrees;\n\t\t\tif (peggedValue > self.target_temperature) {\n\t\t\t\tdegs += 8;\n\t\t\t} else {\n\t\t\t\tdegs -= 8;\n\t\t\t}\n\t\t\tvar pos = rotatePoint(properties.lblAmbientPosition,degs,[properties.radius, properties.radius]);\n\t\t\tattr(lblAmbient,{\n\t\t\t\tx: pos[0],\n\t\t\t\ty: pos[1]\n\t\t\t});\n\t\t}\n\n\t\t/*\n\t\t * RENDER - target temperature\n\t\t */\n\t\tfunction renderTargetTemperature() {\n\t\t\tlblTarget_text.nodeValue = Math.floor(self.target_temperature);\n\t\t\tsetClass(lblTargetHalf,'shown',self.target_temperature%1!=0);\n\t\t}\n\t\t\n\t\t/*\n\t\t * RENDER - leaf\n\t\t */\n\t\tfunction renderLeaf() {\n\t\t\tsetClass(svg,'has-leaf',self.has_leaf);\n\t\t}\n\t\t\n\t\t/*\n\t\t * RENDER - HVAC state\n\t\t */\n\t\tfunction renderHvacState() {\n\t\t\tArray.prototype.slice.call(svg.classList).forEach(function(c) {\n\t\t\t\tif (c.match(/^dial--state--/)) {\n\t\t\t\t\tsvg.classList.remove(c);\n\t\t\t\t};\n\t\t\t});\n\t\t\tsvg.classList.add('dial--state--'+self.hvac_state);\n\t\t}\n\t\t\n\t\t/*\n\t\t * RENDER - away\n\t\t */\n\t\tfunction renderAway() {\n\t\t\tsvg.classList[self.away ? 'add' : 'remove']('away');\n\t\t}\n\t\t\n\t\t/*\n\t\t * Drag to control\n\t\t */\n\t\tvar _drag = {\n\t\t\tinProgress: false,\n\t\t\tstartPoint: null,\n\t\t\tstartTemperature: 0,\n\t\t\tlockAxis: undefined\n\t\t};\n\t\t\n\t\tfunction eventPosition(ev) {\n\t\t\tif (ev.targetTouches && ev.targetTouches.length) {\n\t\t\t\treturn  [ev.targetTouches[0].clientX, ev.targetTouches[0].clientY];\n\t\t\t} else {\n\t\t\t\treturn [ev.x, ev.y];\n\t\t\t};\n\t\t}\n\t\t\n\t\tvar startDelay;\n\t\tfunction dragStart(ev) {\n\t\t\tstartDelay = setTimeout(function() {\n\t\t\t\tsetClass(svg, 'dial--edit', true);\n\t\t\t\t_drag.inProgress = true;\n\t\t\t\t_drag.startPoint = eventPosition(ev);\n\t\t\t\t_drag.startTemperature = self.target_temperature || options.minValue;\n\t\t\t\t_drag.lockAxis = undefined;\n\t\t\t},1000);\n\t\t};\n\t\t\n\t\tfunction dragEnd (ev) {\n\t\t\tclearTimeout(startDelay);\n\t\t\tsetClass(svg, 'dial--edit', false);\n\t\t\tif (!_drag.inProgress) return;\n\t\t\t_drag.inProgress = false;\n\t\t\tif (self.target_temperature != _drag.startTemperature) {\n\t\t\t\tif (typeof options.onSetTargetTemperature == 'function') {\n\t\t\t\t\toptions.onSetTargetTemperature(self.target_temperature);\n\t\t\t\t};\n\t\t\t};\n\t\t};\n\t\t\n\t\tfunction dragMove(ev) {\n\t\t\tev.preventDefault();\n\t\t\tif (!_drag.inProgress) return;\n\t\t\tvar evPos =  eventPosition(ev);\n\t\t\tvar dy = _drag.startPoint[1]-evPos[1];\n\t\t\tvar dx = evPos[0] - _drag.startPoint[0];\n\t\t\tvar dxy;\n\t\t\tif (_drag.lockAxis == 'x') {\n\t\t\t\tdxy  = dx;\n\t\t\t} else if (_drag.lockAxis == 'y') {\n\t\t\t\tdxy = dy;\n\t\t\t} else if (Math.abs(dy) > properties.dragLockAxisDistance) {\n\t\t\t\t_drag.lockAxis = 'y';\n\t\t\t\tdxy = dy;\n\t\t\t} else if (Math.abs(dx) > properties.dragLockAxisDistance) {\n\t\t\t\t_drag.lockAxis = 'x';\n\t\t\t\tdxy = dx;\n\t\t\t} else {\n\t\t\t\tdxy = (Math.abs(dy) > Math.abs(dx)) ? dy : dx;\n\t\t\t};\n\t\t\tvar dValue = (dxy*getSizeRatio())/(options.diameter)*properties.rangeValue;\n\t\t\tself.target_temperature = roundHalf(_drag.startTemperature+dValue);\n\t\t}\n\t\t\n\t\tsvg.addEventListener('mousedown',dragStart);\n\t\tsvg.addEventListener('touchstart',dragStart);\n\t\t\n\t\tsvg.addEventListener('mouseup',dragEnd);\n\t\tsvg.addEventListener('mouseleave',dragEnd);\n\t\tsvg.addEventListener('touchend',dragEnd);\n\t\t\n\t\tsvg.addEventListener('mousemove',dragMove);\n\t\tsvg.addEventListener('touchmove',dragMove);\n\t\t//\n\t\t\n\t\t/*\n\t\t * Helper functions\n\t\t */\n\t\tfunction restrictTargetTemperature(t) {\n\t\t\treturn restrictToRange(roundHalf(t),options.minValue,options.maxValue);\n\t\t}\n\t\t\n\t\tfunction angle(point) {\n\t\t\tvar dx = point[0] - properties.radius;\n\t\t\tvar dy = point[1] - properties.radius;\n\t\t\tvar theta = Math.atan(dx/dy) / (Math.PI/180);\n\t\t\tif (point[0]>=properties.radius && point[1] < properties.radius) {\n\t\t\t\ttheta = 90-theta - 90;\n\t\t\t} else if (point[0]>=properties.radius && point[1] >= properties.radius) {\n\t\t\t\ttheta = 90-theta + 90;\n\t\t\t} else if (point[0]<properties.radius && point[1] >= properties.radius) {\n\t\t\t\ttheta = 90-theta + 90;\n\t\t\t} else if (point[0]<properties.radius && point[1] < properties.radius) {\n\t\t\t\ttheta = 90-theta+270;\n\t\t\t}\n\t\t\treturn theta;\n\t\t};\n\t\t\n\t\tfunction getSizeRatio() {\n\t\t\treturn options.diameter / targetElement.clientWidth;\n\t\t}\n\t\t\n\t};\n})();\n\n/* ==== */\n(function(scope) {\n    \n    var nest = new thermostatDial(document.getElementById('thermostat'),{\n    \tonSetTargetTemperature: function(v) {\n    \t\tscope.send({topic: \"target_temperature\", payload: v});\n    \t}\n    });\n\n\n    scope.$watch('msg', function(data) {\n        //console.log(data.topic+\"  \"+data.payload);\n        if (data.topic == \"ambient_temperature\") {\n            nest.ambient_temperature = data.payload;\n        } if (data.topic == \"target_temperature\") {\n            nest.target_temperature = data.payload;\n        } if (data.topic == \"hvac_state\") {\n            nest.hvac_state = data.payload;\n        } if (data.topic == \"has_leaf\") {\n            nest.has_leaf = data.payload;\n        } if (data.topic == \"away\") {\n            nest.away = data.payload;\n        }\n    });\n})(scope);\n\n</script>","storeOutMessages":true,"fwdInMessages":false,"templateScope":"local","x":710,"y":300,"wires":[["602fed2a.3f1da4"]]},{"id":"121fdb16.06c045","type":"function","z":"2c174318.8fafec","name":"ambient_temperature","func":"flow.set(\"ambient_temp_topic\", msg.topic); \nflow.set(\"ambient_temp_payload\", msg.payload); \nmsg.topic = \"ambient_temperature\";\n\nreturn msg;","outputs":1,"noerr":0,"x":500,"y":300,"wires":[["85e9547.e3387a8"]]},{"id":"cde4777b.ed2518","type":"function","z":"2c174318.8fafec","name":"target_temperature","func":"flow.set(\"target_temp_topic\", msg.topic); \nflow.set(\"target_temp_payload\", msg.payload); \nmsg.topic = \"target_temperature\";\nreturn msg;","outputs":1,"noerr":0,"x":490,"y":340,"wires":[["85e9547.e3387a8"]]},{"id":"919595ac.732ca8","type":"function","z":"2c174318.8fafec","name":"hvac_state","func":"msg.topic = \"hvac_state\";\nreturn msg;","outputs":1,"noerr":0,"x":450,"y":400,"wires":[["85e9547.e3387a8"]]},{"id":"2fb888e5.40db78","type":"function","z":"2c174318.8fafec","name":"has_leaf","func":"msg.topic = \"has_leaf\";\nreturn msg;","outputs":1,"noerr":0,"x":440,"y":440,"wires":[["85e9547.e3387a8"]]},{"id":"d5e8585d.3e0038","type":"function","z":"2c174318.8fafec","name":"away","func":"msg.topic = \"away\";\nreturn msg;","outputs":1,"noerr":0,"x":430,"y":480,"wires":[["85e9547.e3387a8"]]},{"id":"595d8c0.c132074","type":"inject","z":"2c174318.8fafec","name":"","topic":"has_leaf","payload":"true","payloadType":"bool","repeat":"","crontab":"","once":true,"x":170,"y":520,"wires":[["2fb888e5.40db78"]]},{"id":"b99a72e7.57614","type":"inject","z":"2c174318.8fafec","name":"","topic":"has_leaf","payload":"false","payloadType":"bool","repeat":"","crontab":"","once":false,"x":170,"y":560,"wires":[["2fb888e5.40db78"]]},{"id":"62f528cc.4f7cd8","type":"inject","z":"2c174318.8fafec","name":"","topic":"away","payload":"true","payloadType":"bool","repeat":"","crontab":"","once":false,"x":160,"y":600,"wires":[["d5e8585d.3e0038"]]},{"id":"4105734f.e0fb3c","type":"inject","z":"2c174318.8fafec","name":"","topic":"away","payload":"false","payloadType":"bool","repeat":"","crontab":"","once":false,"x":160,"y":640,"wires":[["d5e8585d.3e0038"]]},{"id":"52530d07.ac8b64","type":"inject","z":"2c174318.8fafec","name":"","topic":"hvac_state","payload":"off","payloadType":"str","repeat":"","crontab":"","once":false,"x":170,"y":400,"wires":[["919595ac.732ca8"]]},{"id":"1fa4d3.85fffb2d","type":"inject","z":"2c174318.8fafec","name":"","topic":"hvac_state","payload":"heating","payloadType":"str","repeat":"","crontab":"","once":false,"x":190,"y":440,"wires":[["919595ac.732ca8"]]},{"id":"9cd66a26.829668","type":"inject","z":"2c174318.8fafec","name":"","topic":"hvac_state","payload":"cooling","payloadType":"str","repeat":"","crontab":"","once":false,"x":190,"y":480,"wires":[["919595ac.732ca8"]]},{"id":"fc61a2c4.d585d","type":"inject","z":"2c174318.8fafec","name":"","topic":"ambient_temperature","payload":"21.5","payloadType":"num","repeat":"","crontab":"","once":true,"x":230,"y":300,"wires":[["121fdb16.06c045"]]},{"id":"f38cf4a3.ae78a8","type":"inject","z":"2c174318.8fafec","name":"","topic":"target_temperature","payload":"20","payloadType":"num","repeat":"","crontab":"","once":true,"x":220,"y":340,"wires":[["cde4777b.ed2518"]]},{"id":"602fed2a.3f1da4","type":"function","z":"2c174318.8fafec","name":"target_temperature","func":"if (msg.topic == \"target_temperature\") {\n    flow.set(\"target_temp_topic\", msg.topic); \n    flow.set(\"target_temp_payload\", msg.payload); \n    return msg;\n}","outputs":1,"noerr":0,"x":890,"y":300,"wires":[["89041053.48c4f"]]},{"id":"89041053.48c4f","type":"debug","z":"2c174318.8fafec","name":"","active":true,"console":"false","complete":"false","x":1090,"y":300,"wires":[]},{"id":"e9312d8.4023cd","type":"ui_text","z":"2c174318.8fafec","group":"8db78a1d.f45e08","order":0,"width":0,"height":0,"name":"","label":"AmbientTemp","format":"{{msg.payload}}","layout":"row-spread","x":780,"y":60,"wires":[]},{"id":"18a62e57.881fe2","type":"function","z":"2c174318.8fafec","name":"Ambient_Temp im Flow Speichern","func":"var t_topic = flow.get(\"ambient_temp_topic\");\nvar t_payload = flow.get(\"ambient_temp_payload\");\nmsg.payload = t_payload;\nmsg.topic = t_topic;\n\nreturn msg;\n\n\n","outputs":1,"noerr":0,"x":480,"y":100,"wires":[["e9312d8.4023cd","dd132023.ada06","85e9547.e3387a8"]]},{"id":"dd132023.ada06","type":"debug","z":"2c174318.8fafec","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":770,"y":120,"wires":[]},{"id":"88ebaa81.cc5e78","type":"ui_ui_control","z":"2c174318.8fafec","name":"","x":210,"y":100,"wires":[["18a62e57.881fe2"]]},{"id":"6328dc39.79dae4","type":"ui_text","z":"2c174318.8fafec","group":"8db78a1d.f45e08","order":0,"width":0,"height":0,"name":"","label":"TargetTemp","format":"{{msg.payload}}","layout":"row-spread","x":790,"y":680,"wires":[]},{"id":"7dd3f4ef.cfe85c","type":"function","z":"2c174318.8fafec","name":"Target_Temp im Flow speichern","func":"var t_topic = flow.get(\"target_temp_topic\");\nvar t_payload = flow.get(\"target_temp_payload\");\nmsg.payload = t_payload;\nmsg.topic = t_topic;\n\nreturn msg;\n\n\n","outputs":1,"noerr":0,"x":510,"y":720,"wires":[["6328dc39.79dae4","ab8ff2e2.a6156","85e9547.e3387a8"]]},{"id":"ab8ff2e2.a6156","type":"debug","z":"2c174318.8fafec","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":790,"y":740,"wires":[]},{"id":"712ded86.c2fbe4","type":"ui_ui_control","z":"2c174318.8fafec","name":"","x":230,"y":720,"wires":[["7dd3f4ef.cfe85c"]]},{"id":"8db78a1d.f45e08","type":"ui_group","z":"","name":"Default","tab":"135cb0cd.dc652f","disp":true,"width":"6","collapse":false},{"id":"135cb0cd.dc652f","type":"ui_tab","z":"","name":"Test","icon":"build"}]

Master_Nick

Na, ob wir bei 4613 Aufrufe und 50 Antworten bald eine eigene Node-RED Sektion bekommen ;-D ?
Wobei es einfach zu wenig - und das ist ja sehr schön - Probleme gibt mit Node-RED *hehe*

Ich bin auch super zufrieden eben genau die Punkte Geschwindigkeit und Träge Reaktion haben mich von den anderen weg zu Node-RED geführt.
Auch das effektiv soviele Instanzen wie man will mithören könnten ohne eine Trägheit hinein zu bringen ist was feines.
Rancher K8s Cluster mit nanoCUL (a-culfw) | IObroker | IT(V1&V3), IT-PIR, THGR122NX |Co² | alexa-fhem | WOL | NFC | Harmony UltimateHub | Anwesenheitserkennnung | Roomba | 10" Touch mit Node-Red | SonOff S20 | SonOff Touch | SonOff Dual | Rolladen | Und ganz viel anderes tolles Gerödel.... ;-)

Ranseyer

Das Thema finde ich super spannend. Aus Zeitgründungen werde ich mich vermutlich erst im Winter dranhängen. Aber ich lese mal mit.

PS: Ich habe außer NodeRed eigentlich schon alles angetestet. Im Moment nutze ich FTUI. Daran stört mich der für mich sehr hohe Pflegeaufwand und die extreme Trägheit auf alten Tablets. Allerdingt gibt es halt schon sehr viele fertige Zutaten.

IOBroker wäre für mich von der Idee genau richtig, ist mir aber zu unzuverlässig beim Konfigurieren und ich vertraue dem nicht genügend. Außerdem frisst es auch mehr Zeit als man annehmen könnte.
FHEM mit FTUI. Homematic-Funk für Thermostate und Licht. MySensors als Basis für eigene HW.
Zentrale ist der MAPLE-CUL mit RFM69+HModUART-AddOn.
Doku zu meinen Projekten: Github/Ranseyer. Platinen falls verfügbar gerne auf Anfrage.
Support: gerne wenn ich Zeit+Lust habe im Forum. Nicht per PN!

Master_Nick

Du wirst dich wundern wie schnell du in Node-RED auf brauchbare Ergebnisse kommst.  :D ;D
Rancher K8s Cluster mit nanoCUL (a-culfw) | IObroker | IT(V1&V3), IT-PIR, THGR122NX |Co² | alexa-fhem | WOL | NFC | Harmony UltimateHub | Anwesenheitserkennnung | Roomba | 10" Touch mit Node-Red | SonOff S20 | SonOff Touch | SonOff Dual | Rolladen | Und ganz viel anderes tolles Gerödel.... ;-)

Shojo

Zitat von: Ranseyer am 08 März 2018, 17:00:40
Im Moment nutze ich FTUI. Daran stört mich der für mich sehr hohe Pflegeaufwand und die extreme Trägheit auf alten Tablets. Allerdingt gibt es halt schon sehr viele fertige Zutaten.

IOBroker wäre für mich von der Idee genau richtig, ist mir aber zu unzuverlässig beim Konfigurieren und ich vertraue dem nicht genügend


Das unterschreibe ich so, das sind auch genau meine Erfahrungen.
FHEM auf: Shuttle PC (x64) (Docker)
Bridge: SignalESP 433mHz, ConBee (deCONZ in Docker)
Rest: ESP8266, SONOFF, Sonos, Echo Dot, Xiaomi Vacuum (root), ESP RGBWW Wifi Led Controller, Node-RED, LEDMatrix, Pixel It

hermann1514

Hallo zusammen,

ich habe nun auch mein TabletUI zur Seite gelegt und bin mit NodeRed angefangen. Wenn man sich mal ein wenig eingearbeitet hat ist das aus Supi und läuft wie S.. :-)

Nun, ich habe meine historischen Daten wie Temperatur, ValvePostion usw. in eine INFLUXDB liegen und habe die Graphen mit Grafana erstellt. Ich möchte nun aber mit den INFLUXDB Plugin für NodeRed die Grapgen direkt in NODERED erstellen.
Habe schon einiges probiert - aber dammit bin ich noch nicht weitergekommen.

Kann mir jemand helfen?

Danke.
Gruß
Hermann

Master_Nick

Moin  ;)

Schön wie das Thema immer mehr Zuwachs erfährt!  8)

Ich persönlich habe noch keine Graphen in Node-RED realisiert, auch der geplante Umstieg auf eine Log-DB steht noch weiter aus  ::). Ich habe aber wohl eine Seite mit 5 Thermostaten und 6 Hygrometern/Thermometern. Diese Seite hat beim initialen Laden schon eine gewisse Ladezeit (auf S7 edge, Raspi 3 und Windows PC) durch das Abfragen/Verarbeiten der verschiedenen Werte und Anzeigen, die anschließende dauerhafte Live-Anzeige ist verlustfrei.

Daher habe ich für mich entschieden nur aktuelle Werte auf Node-ERED anzeigen zu lassen, da es mir hauptsächlich um eine komfortable übersichtliche Steuerung des Systems geht. Graphen kann ich in der FHEM Web-Instanz einsehen oder wie ich finde noch komfortabler über andFHEM am Handy.
Rancher K8s Cluster mit nanoCUL (a-culfw) | IObroker | IT(V1&V3), IT-PIR, THGR122NX |Co² | alexa-fhem | WOL | NFC | Harmony UltimateHub | Anwesenheitserkennnung | Roomba | 10" Touch mit Node-Red | SonOff S20 | SonOff Touch | SonOff Dual | Rolladen | Und ganz viel anderes tolles Gerödel.... ;-)

Shojo

Zitat von: Master_Nick am 12 März 2018, 11:28:46Diese Seite hat beim initialen Laden schon eine gewisse Ladezeit (auf S7 edge, Raspi 3 und Windows PC) durch das Abfragen/Verarbeiten der verschiedenen Werte und Anzeigen, die anschließende dauerhafte Live-Anzeige ist verlustfrei.

Das hat damit zu tun das Node-Red diese Daten als Json ab Webbrowser gibt und der dann lange rechnet. (da es zum teil doch schon sehr viele Messungen sind die da zusammen kommen)
Wenn man allerdings z.B. vorberechnete 15min, 30min oder Stunden Durchschnittswerte übergibt bleibt es schnell ;)

FHEM auf: Shuttle PC (x64) (Docker)
Bridge: SignalESP 433mHz, ConBee (deCONZ in Docker)
Rest: ESP8266, SONOFF, Sonos, Echo Dot, Xiaomi Vacuum (root), ESP RGBWW Wifi Led Controller, Node-RED, LEDMatrix, Pixel It

Master_Nick

Mhh - ich glaube das hilft dann nur bei Graphen.

Bei Raumtemperatursteuerung und aktueller Temperatur helfen Durchschnittswerte aus meiner Sicht weniger.  ;D
Rancher K8s Cluster mit nanoCUL (a-culfw) | IObroker | IT(V1&V3), IT-PIR, THGR122NX |Co² | alexa-fhem | WOL | NFC | Harmony UltimateHub | Anwesenheitserkennnung | Roomba | 10" Touch mit Node-Red | SonOff S20 | SonOff Touch | SonOff Dual | Rolladen | Und ganz viel anderes tolles Gerödel.... ;-)

Shojo

Achsooo, bei dir ist das auch langsam ohne Graphen?

Das kann ich dann aber so nicht bestätigen, ich habe 14 auf einer Seite und die kommt so  schnell wie die anderen.
FHEM auf: Shuttle PC (x64) (Docker)
Bridge: SignalESP 433mHz, ConBee (deCONZ in Docker)
Rest: ESP8266, SONOFF, Sonos, Echo Dot, Xiaomi Vacuum (root), ESP RGBWW Wifi Led Controller, Node-RED, LEDMatrix, Pixel It