Navigation

    Logo
    • Register
    • Login
    • Search
    • Recent
    • Tags
    • Unread
    • Categories
    • Unreplied
    • Popular
    • GitHub
    • Docu
    • Hilfe
    1. Home
    2. Deutsch
    3. Visualisierung
    4. Dashboard für Temp/Hum mit Flot im grafana-Stil

    NEWS

    • Neuer Blog: Fotos und Eindrücke aus Solingen

    • ioBroker@Smart Living Forum Solingen, 14.06. - Agenda added

    • ioBroker goes Matter ... Matter Adapter in Stable

    Dashboard für Temp/Hum mit Flot im grafana-Stil

    This topic has been deleted. Only users with topic management privileges can see it.
    • OliverIO
      OliverIO last edited by OliverIO

      Hallo,

      ich habe mir ein neues Dashboard für meine Temperatur und Feuchtigkeitswerte gebaut.
      Da mir der Stil von Grafana sehr gut gefallen hat, aber zum einen Grafana für mein kleines Anzeigetablet zu viel Overhead mitschleift, habe ich das mittels Flot nachgebaut.
      Übrigens nutzt grafana unter der Oberfläche ebenfalls flot, allerdings unter mehreren logischen Schichten an modulen verborgen (flot->businesscharts->react ->angular).

      Mein dashboard sieht wie folgt aus:
      ff994e87-6862-4fa1-ae3d-8306a09df9c2-image.png
      Durch touch auf die jeweilige Anzeige kommt die Detailanzeige, die durch einen erneuten Touch auch wieder geschlossen wird. Aktuell hier immer nur die 24h Sicht. Erweitern will ich das noch mit 2 oder 3 anderen durch Knopf umschaltbare Zeiten.
      9d4f1403-3839-43b9-ba4d-005328fa8822-image.png

      Um felxibel in der Anpassung zu sein hab ich mir hier keine eigenen Widgets gebaut, sondern
      die Umsetzung mittels eines Adapters und verschiedenen basic HTML-widgets gelöst, die HTML und javascript-Befehle enthalten.
      Im Adapter sind die ganzen Bibliotheken für flot, flot-gauge und meinen ganzen Backendcode mit den Floteinstellungen enthalten.
      In den Basic-Html widgets zum einen eines, in dem die ganzen Bibliotheken ins vis geladen werden und dann für jede einzelne Anzeige ein kurzer javascript-Block mit den Parametern, die dort angezeigt werden sollen.

      Inhalt html widget zum libs-laden

      <script type="text/javascript" src="http://192.168.1.61:8082/mydashboard/js/flot.gauge/js/jquery.flot.js"></script>
      <script type="text/javascript" src="http://192.168.1.61:8082/mydashboard/js/flot/jquery.flot.time.js"></script>
      <script type="text/javascript" src="http://192.168.1.61:8082/mydashboard/js/flot.gauge/jquery.flot.gauge.grafana.js"></script>
      <script type="text/javascript" src="http://192.168.1.61:8082/mydashboard/js/index.js"></script>
      
      </div>
      <script>
      getGauge('Küche','humidity','#gaugekitchen',200,220);
      getTemp('Küche','temperature','#tempkitchen',200,220);
      </script>
      
      


      Beispiel html-widget zur Anzeige der Feuchtigkeitsanzeige

      <div id="gaugekitchen" style=""></div>
      


      Beispiel html-widget zur Anzeige der Temperaturanzeige

      <div id="tempkitchen" class="tempValue"></div>
      

      Mein Backendjavascript im Adapter sieht so aus:

      function getSQLDate(device,parameter,callback,type) {
        
          var range12h = 1000*60*60*12;
          var range24h = 1000*60*60*24;
          if (type=='24h') var myQuery = "SELECT ts, parameter, val FROM iobroker.mihome_th where device = '" + device +"' and parameter in ('humidity','temperature') AND val is not null AND ts > unix_timestamp()*1000-((60*60*24)*1000) ORDER BY parameter,ts";
          if (type=='last') var myQuery = "SELECT ts,val FROM iobroker.mihome_th where device = '" + device +"' and parameter = '" + parameter +"' and val is not NULL order by ts desc limit 1";
          
          vis.conn._socket.emit('sendTo', 'sql.0', 'query', myQuery, function (callback,type,result) {
      
              if (result.error) {
                  console.error(result.error);
              } else {
                  
                  if (type=='last') {
                      var datapoints = Object.keys(result.result).map(function(key) {
                          return [result.result[key].ts, result.result[key].val];
                      });
                  }
                  if (type=='24h') {
                      var datapoints = result.result.reduce(function(acc,obj) {
                          var key = obj['parameter'];
                              if(!acc[key]) {
                                  acc[key] = [];
                              }
                              acc[key].push([obj.ts,obj.val]);
                              return acc;
                      },[]);
                  }
                  callback(datapoints);
                  
              }
          }.bind(this,callback,type));
      
      }
      function humFormatter(v, axis) {
          return v.toFixed(axis.tickDecimals) + " %H";
      }
      function tempFormatter(v, axis) {
          return v.toFixed(axis.tickDecimals) + " °C";
      }    
      
      
                          
                         function getTemp(device,parameter,placeholder,width,height) {
                              var data = {};
                              data.device = device;
                              data.parameter = parameter;
                              data.placeholder = placeholder;
                              getSQLDate(device,parameter,doTemp.bind(data),'last');                                                
                          }
                           function doTemp(sqldata) {
                              var data = this;
                              $(data.placeholder).html(tempFormatter(sqldata[0][1]));
                              getTempUpdate(data);
                          }
                          function getTempUpdate(data) {
                              getSQLDate(data.device,data.parameter,doTempUpdate.bind(data),'last');                        
                          }
                          function doTempUpdate(sqldata) {
                              var data = this;
                              $(data.placeholder).html(tempFormatter(sqldata[0][1]));
                              setTimeout(function() {
                                  getTempUpdate(data);
                              }.bind(data),5000);
                          }
                          
                          function tempFormatter(value) {
                              return Math.round(value) + "&deg;C";
                          }
                          
                          function getTempchart(device,parameter,placeholder,width,height,type='inside') {
                              var data = {};
                              data.placeholder = placeholder;
                              data.width = width;                        
                              data.height = height;
                              data.device = device;
                              data.type = type;
                              getSQLDate(device,parameter,doTempchart.bind(data),'24h');  
                          }
                          function doTempchart(sqldata) {
                              var data = this;
                              var new_tempdata = $.extend(true,{},temp_data);
                              new_tempdata[0].data = sqldata.temperature;
                              new_tempdata[1].data = sqldata.humidity;
                              debugger;
                              $(data.placeholder).width(data.width)
                                  .height(data.height);
                              if (data.type=='inside') data.options = $.extend(true,{},temp_inside_options);
                              if (data.type=='outside') data.options = $.extend(true,{},temp_outside_options);
                              data.plot = $.plot(data.placeholder,[new_tempdata[0],new_tempdata[1]], data.options);
      
                          }
      
                          
                          function getGauge(device,parameter,placeholder,width,height,type='inside') {
                              var data = {};
                              data.placeholder = placeholder;
                              data.width = width;                        
                              data.height = height;
                              data.device = device;
                              data.parameter = parameter;
                              data.type = type;
                              getSQLDate(device,parameter,doGauge.bind(data),'last');                        
                          }
                          function doGauge(sqldata) {
                              var data = this;
                              var new_humdata = hum_data.slice(0);
                              new_humdata[0].data = sqldata;
                              new_humdata[0].label = this.device;
                              
                              if (data.type=='inside') data.options = $.extend(true,{},hum_threshold_inside,hum_options);
                              if (data.type=='outside') data.options = $.extend(true,{},hum_threshold_outside,hum_options);
                              $(this.placeholder).width(this.width)
                                  .height(this.height);
                              data.plot = $.plot(this.placeholder,new_humdata, data.options);
                                  $(this.placeholder).click(function() {
                                      getTempchart(data.device,null,'#tempdialog',880,500,data.type);
                                      $('#tempdialog').dialog({
                                          dialogClass: 'noTitle',
                                          minHeight: 527,
                                          maxHeight: 527,
                                          minWidth: 900,
                                          maxWidth: 900,
                                          position: [0,28],
                                      }).on('click',function(event){
                                          $('#tempdialog').dialog('close');
                                      });
                                      return false;
                                  });
                              getGaugeUpdate(data);
                          }
                          function getGaugeUpdate(data) {
                              getSQLDate(data.device,data.parameter,doGaugeUpdate.bind(data),'last');                        
                          }
                          function doGaugeUpdate(sqldata) {
                              
                              var data = this;
                              var new_humdata = hum_data.slice(0);
                              new_humdata[0].data = sqldata;
                              new_humdata[0].label = this.device;
                              data.options.series.gauges.value.color = getColor(data.options.series.gauges,new_humdata[0].data[0][1]);
                              data.plot = $.plot(this.placeholder,new_humdata, data.options);
                              setTimeout(function() {
                                  getGaugeUpdate(data);
                              }.bind(data),5000);
                          }
                          function getColor(gaugeOptionsi, data) {
                              var color;
                              for (var i = 0; i < gaugeOptionsi.threshold.values.length; i++) {
                                  var threshold = gaugeOptionsi.threshold.values[i];
                                  color = threshold.color;
                                  if (data < threshold.value) {
                                      break;
                                  }
                              }
                              return color;
                          }
         
        
      
      var hum_data = [
                          {
                          label: "device",
                          data: [[0, 50]]
                          }
                        ];
                                
                        
      var hum_options = {
          series: {
            gauges: {
              frame: false,
              gauge: {
                  min: 30,
                  max: 70,
                  width: 15,
                  background: {
                      color: "#2f2f32"
                  },
                  border: {
                      color: "#2f2f32",
                      width: 0
                  },
                  shadow: {
                      show: false,
                  }
              },
              value: {
                  background: {
                      color: null
                  },
                  font: {
                      size: 40, // a specified number, or 'auto'
                      family: ",sans-serif"
                  },
      //            color: "#73bf69"
              },
              cell: {
                  background: {
                      color: null
                  },
                  border: {
                      show: false,
                  },
                  margin: 5,
                  vAlign: "middle" // 'top' or 'middle' or 'bottom'
              },
              debug: {
                  log: true,
                  layout: true
              },
              show: true,
              label: {
                  show: true,
                  color: "#d8d9da",
                  margin: 0,
                  font: {
                      size: 20, // a specified number, or 'auto'
                      family: ",sans-serif"
                  }
              }
            }
          }
        };                    
      var hum_threshold_outside = {
          series: {
            gauges: {
              threshold: {
                  values: [
                      {
                          value: 40,
                          color: "#73bf69"
                      }, {
                          value: 60,
                          color: "#73bf69"
                      }, {
                          value: 100,
                          color: "#73bf69"
                      },
                  ]
              }
            }
          }
      };
      var hum_threshold_inside = {
          series: {
            gauges: {
              threshold: {
                  values: [
                      {
                          value: 40,
                          color: "#ee485a"
                      }, {
                          value: 60,
                          color: "#73bf69"
                      }, {
                          value: 100,
                          color: "#ee485a"
                      },
                  ]
              }
            }
          }
      };
      
      var temp_data =  [
                      { 
                          data: null, 
                          color: "#F2495C",
                          bars: {
                              fillColor: "#F2495C"
                          },                    
                          label: "temp"
                      },
                      {   data: null,
                          color: "#5794F2",
                          bars: {
                              fillColor: "#5794F2"
                          },                    
                          label: "hum", 
                          yaxis: 2 
                      }
                  ];
      var temp_inside_options =  {
                          series: {
                              lines: {
                                  lineWidth: 2,
                                  fill: 0.1
                              }
                          },
                          xaxes:  [ 
                                      { 
                                          mode: "time", 
                                          timeformat: "%H:%M",
                                          color: "#464648",
                                          font: {
                                              color: "#d8d9da"
                                          }
                                      } 
                          ],
                          yaxes:  [ 
                                      { 
                                          min: 5,
                                          max: 35,
                                          tickFormatter: tempFormatter,
                                          color: "#464648",
                                          font: {
                                              color: "#d8d9da"
                                          }
      
                                      }, 
                                      {
                                          min: 30,
                                          max: 90,
                                          alignTicksWithAxis: 1,
                                          position: "rigth",
                                          tickFormatter: humFormatter,
                                          color: "#464648",
                                          font: {
                                              color: "#d8d9da"
                                          }
      
                                      } 
                          ],
                          legend: { position: "sw" },
                          grid: {
                              borderWidth:    0,
                              color:          "#464648",
                              labelMarginX:   0,
      
                          }
                          };
      var temp_outside_options =  {
                          series: {
                              lines: {
                                  lineWidth: 2,
                                  fill: 0.1
                              }
                          },
                          xaxes:  [ 
                                      { 
                                          mode: "time", 
                                          timeformat: "%H:%M",
                                          color: "#464648",
                                          font: {
                                              color: "#d8d9da"
                                          }
                                      } 
                          ],
                          yaxes:  [ 
                                      { 
                                          min: -15,
                                          max: 35,
                                          tickFormatter: tempFormatter,
                                          color: "#464648",
                                          font: {
                                              color: "#d8d9da"
                                          }
      
                                      }, 
                                      {
                                          min: 30,
                                          max: 100,
                                          alignTicksWithAxis: 1,
                                          position: "rigth",
                                          tickFormatter: humFormatter,
                                          color: "#464648",
                                          font: {
                                              color: "#d8d9da"
                                          }
      
                                      } 
                          ],
                          legend: { position: "sw" },
                          grid: {
                              borderWidth:    0,
                              color:          "#464648",
                              labelMarginX:   0,
      
                          }
                          };
      
      

      Für die Historydaten habe ich mir auf Basis der iobroker-Daten noch eine eigene View gebaut, da ich mit den kryptischen Datenpunktbezeichnungen nicht arbeiten wollte, die mit der Xiaomi mihome-Adapter da produziert.

      Auch bei der Erstellung des dashboards in vis sind noch ein paar Dinge zu beachten, da die iobroker-App zusätzlich geladene Bibiotheken direkt nicht mag. Daher muss man die erstellte view nochmals über eine weitere view in einem iframe anzeigen. dann klappt es.

      Meine Lösung ist etwas frickelig und der Code auch nicht optimal strukturiert. Daher bei Übernahme des Konzeptes sollte man sich etwas besser mit javascript, html, sql und vis auskennen. Es ist keine CopyPaste-Lösung.

      Bei Interesse kann ich die anderen Bibliotheken ebenfalls bereitstellen (github).
      Bei noch mehr Interesse könnte ich das auch mal per teamviewer vorstellen oder gar per WebEx mehreren gleichzeitig.

      1 Reply Last reply Reply Quote 5
      • R
        Reongard last edited by

        wow, das sieht sehr übersichtlich aus. Respekt! Ich bin mit meiner Vis noch lange nicht soweit aber werde mir das bei Gelegenheit vielleicht mal kopieren 😉

        1 Reply Last reply Reply Quote 0
        • M
          Malz1902 last edited by

          Hallo, ich fange gerade mit Vis an. Ich habe in allen Zimmern im Haus von Aqara die Sensoren für die Temperatur und Luftfeuchtigkeit. Das mit der Grafischen Anzeige gefällt mir sehr gut. Wie kann ich das nachbauen? Ich bin irgendwie zu doof dafür

          OliverIO 1 Reply Last reply Reply Quote 0
          • OliverIO
            OliverIO @Malz1902 last edited by

            @Malz1902 an welcher stelle hängst du?

            1 Reply Last reply Reply Quote 0
            • M
              Malz1902 last edited by

              Puh ja, ganz am Anfang 😄 Weil irgendwie hab ich absolut Null Plan wie ich vorgehen muss. Also ich hätte gerne einfach nur so 5 Grafen für meine Räume wie auf deinen ersten Bild zu sehen. Ich muss da nicht unbedingt reinklicken können. Ich möchte gerne nur die Luftfeuchtigkeit und Temperatur ablesen können

              OliverIO 1 Reply Last reply Reply Quote 0
              • OliverIO
                OliverIO @Malz1902 last edited by

                @Malz1902 hm, ja, ich hatte ja geschrieben, dass man sich ein wenig auskennen muss, da man sicherlich Dinge adaptieren muss.
                Anfangen kannst du wie folgt, evtl hast du da auch schon Schritte bereits erledigt, evtl kommt man auch anders ins Ziel, aber ich beschreibe den Weg, den ich gegangen bin.
                Wie gesagt, da du Sachen adaptieren muss, musst du dich in javascript, sql,html und vis auskennen. Wenn du da noch Schwierigkeiten hast oder den Aufwand scheust dich da einzulesen, dann würde ich dir nicht zu dieser Lösung raten.

                1.) MySQL/MariaDB auf deinem System installieren. ggfs. auch noch eine adminoberfläche für mysql/mariadb wie bspw phpmyadmin
                2) SQL-History Adapter im iobroker installieren und konfigurieren, das er MySQL/MariaDB findet und Zugriff hat
                3) gewünschte Datenpunkte auf aufzeichnen stellen
                4) je nachdem wie einfach du es später haben möchtest, musst du Mappingtabellen und einen sql-view erstellen, so das deine Selektionskriterien möglichst einfach abfragbar sind. Ich bspw muss in meine sqlabfragen nur den Raum und den Parameter einsetzen um alle Werte zu erhalten. Alternativ musst du die sql-abfragen im skript index.js entsprechend anpassen, dabei hilft dir phpmyadmin
                5. Danach musst du dir selbst einen adapter erstellen, in dem du die ganzen backendskripte und notwendigen bibliotheken ablegst, so dass diese in vis dann geladen werden können.
                --- bis hier bewegten wir uns rein nur auf der backendebene
                6. in vis die schnipsel weiter oben eintragen und ggfs. auf deine persönlichen bedürfnisse anpassen.

                1 Reply Last reply Reply Quote 1
                • First post
                  Last post

                Support us

                ioBroker
                Community Adapters
                Donate

                807
                Online

                31.7k
                Users

                79.9k
                Topics

                1.3m
                Posts

                vis
                3
                6
                2801
                Loading More Posts
                • Oldest to Newest
                • Newest to Oldest
                • Most Votes
                Reply
                • Reply as topic
                Log in to reply
                Community
                Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen
                The ioBroker Community 2014-2023
                logo