▁ ▂ ▃ ▄ ▅ ▆ ▇ █ Einfacher RSSI Bargraph im Status

Begonnen von Torxgewinde, 16 März 2025, 18:21:58

Vorheriges Thema - Nächstes Thema

Beta-User

Zitat von: Torxgewinde am 21 März 2025, 10:51:05Habe das mal über die Windrose gelegt... Ich denke es ist das, was dir vorschwebte.
Das sieht ziemlich genau so aus, was ich vor Augen hatte! Sehr cool, Danke! (auch für's posten in "Icons"!)
Die Lösung scheint ansprechend zu sein, immerhin 7 downloads in einem halben Tag - wow!!!
Zitat von: Damian am 21 März 2025, 13:14:27Ich benutze das Icon wind.svg und drehe es über transform-rotate:
Auch das ist eine optisch ansprechende Lösung!
V.a. die Visualisierung der (unterschiedlichen) Geschwindigkeiten ist auf die Art auch ansprechend gelöst, ich hatte dazu eigentlich eine entsprechende (Warn-) Farbgebung des Pfeils vor Augen.

Mal sehen, was es am Ende wird, vermutlich baue ich den Code dann in GW1000_TCP ein, vermutlich landet es erst mal in contrib :) .
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: MiLight@ESP-GW, BT@OpenMQTTGw | MySensors: seriell, v.a. 2.3.1@RS485 | ZWave | ZigBee@deCONZ | SIGNALduino | MapleCUN | RHASSPY
svn: u.a MySensors, Weekday-&RandomTimer, Twilight,  div. attrTemplate-files

Damian

Zitat von: Beta-User am 21 März 2025, 20:59:11V.a. die Visualisierung der (unterschiedlichen) Geschwindigkeiten ist auf die Art auch ansprechend gelöst, ich hatte dazu eigentlich eine entsprechende (Warn-) Farbgebung des Pfeils vor Augen.

Die Farbe passt sich der Windgeschwindigkeit an.

Stammt aus dem Projekt "Wetterstation Umsonst": https://forum.fhem.de/index.php?topic=119612.msg1140489#msg1140489

Gibt es auch im Wiki: https://wiki.fhem.de/wiki/DOIF/uiTable_Schnelleinstieg#Visualisierung:_Wetterstation

Die Funktionen lassen sich natürlich auch direkt beim jeweiligen Device in devStateIcon aufrufen: https://forum.fhem.de/index.php?topic=119612.msg1141738#msg1141738
Programmierte FHEM-Module: DOIF-FHEM, DOIF-Perl, DOIF-uiTable, THRESHOLD, FHEM-Befehl: IF

Torxgewinde

Ich habe mich nochmal an den eigentlichen Bargraph gesetzt und graue Hintergrundbalken hinzugefügt, ich denke das sieht besser aus. Auch sollte man die Chance nicht ungenutzt lassen, die Übergänge mit einer Animation aufzuhübschen. Da FHEM die SVGs allerdings komplett ersetzt, gibt es zZ keine Animationen. Die Datei ist ist angefügt, so sieht das dann aus:

Du darfst diesen Dateianhang nicht ansehen.

Auch wollte ich das WLAN-Signal, Min, Max einfacher berücksichtigen können. Da das SVG auch mit Werten größer 100 und kleiner 0 klarkommt ist die Formel einfach gehalten:
attr Terrassenlicht devStateIcon {\
  return FW_makeImage('light_question') if (ReadingsVal($name, "availability", "offline") eq "offline");;\
  \
  my ($min, $max, $signal) = (-100, -40, ReadingsNum($name, 'Wifi_Signal', 0));;\
  my $val = 100 * ($signal - $min) / ($max - $min);;\
  my $bargraph = FW_makeImage('bargraph2');;\
  $bargraph =~ s/(<svg[^>]+style="[^"]*)--rssi:\d{1,3}/$1--rssi:$val;;/;;\
  \
  my $mstate = ReadingsVal($name, "state", "???");;\
  my $stateIco = FW_makeImage($mstate);;\
  \
  return $stateIco."&nbsp;;".$bargraph;;\
}

Torxgewinde

Was jetzt ja noch schön wäre, wenn die Animation auch abläuft. Noch nicht optimiert, aber so als Proof-Of-Concept habe ich versucht die setValueFn() an der richtigen Stelle zu platzieren und im SVG zu speichern. Das geht auch (Juhu). Was ich nur lieber hätte ist, Zugriff auf das Readings Wert bei folgendem Testdevice - ich will ja nicht immer nur State dafür nutzen. Jemand eine Idee?

defmod bargraphSVG dummy
attr bargraphSVG devStateIcon {\
    my $val = 100+ReadingsVal($name, 'wert', '???');;\
    my $bargraph = FW_makeImage('bargraph2');;\
    $bargraph =~ s/(<svg[^>]+style="[^"]*)--rssi:\d{1,3}/$1--rssi:$val/;;\
    \
    return $bargraph;;\
}
attr bargraphSVG readingList wert
attr bargraphSVG setList wert
attr bargraphSVG userReadings state:wert:.* {\
    return ReadingsVal($name, "wert", "???");;\
}
attr bargraphSVG webCmd wert -100:wert -82:wert -55:wert -40:wert -10:wert -3:wert 0

Das SVG hat nun eben auch noch ein JS um setValueFn() zu platzieren:
<svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" style="--rssi:100">
  <style>
    #bar00 {
      height: 1%;
      y: 99%;
      fill: #ff0000; /* Red for 0% */
    }

    /* For bars from 10% to 100% */
    #bar10 {
      --cmp: max(0, calc(var(--rssi) - 10));
      --cmp2: min(1, calc(var(--cmp)));
      height: calc(var(--cmp2) * 10%);
      y: calc(100% - var(--cmp2) * 10%);
      fill: #ff0000; /* Red for 10% */
    }

    #bar20 {
      --cmp: max(0, calc(var(--rssi) - 20));
      --cmp2: min(1, calc(var(--cmp)));
      height: calc(var(--cmp2) * 20%);
      y: calc(100% - var(--cmp2) * 20%);
      fill: #ff5500; /* Orange for 20% */
    }

    #bar30 {
      --cmp: max(0, calc(var(--rssi) - 30));
      --cmp2: min(1, calc(var(--cmp)));
      height: calc(var(--cmp2) * 30%);
      y: calc(100% - var(--cmp2) * 30%);
      fill: #ff9900; /* Yellow-Orange for 30% */
    }

    #bar40 {
      --cmp: max(0, calc(var(--rssi) - 40));
      --cmp2: min(1, calc(var(--cmp)));
      height: calc(var(--cmp2) * 40%);
      y: calc(100% - var(--cmp2) * 40%);
      fill: #ffcc00; /* Yellow for 40% */
    }

    #bar50 {
      --cmp: max(0, calc(var(--rssi) - 50));
      --cmp2: min(1, calc(var(--cmp)));
      height: calc(var(--cmp2) * 50%);
      y: calc(100% - var(--cmp2) * 50%);
      fill: #ffff00; /* Yellow for 50% */
    }

    #bar60 {
      --cmp: max(0, calc(var(--rssi) - 60));
      --cmp2: min(1, calc(var(--cmp)));
      height: calc(var(--cmp2) * 60%);
      y: calc(100% - var(--cmp2) * 60%);
      fill: #99ff00; /* Light Green for 60% */
    }

    #bar70 {
      --cmp: max(0, calc(var(--rssi) - 70));
      --cmp2: min(1, calc(var(--cmp)));
      height: calc(var(--cmp2) * 70%);
      y: calc(100% - var(--cmp2) * 70%);
      fill: #66cc00; /* Green for 70% */
    }

    #bar80 {
      --cmp: max(0, calc(var(--rssi) - 80));
      --cmp2: min(1, calc(var(--cmp)));
      height: calc(var(--cmp2) * 80%);
      y: calc(100% - var(--cmp2) * 80%);
      fill: #339900; /* Darker Green for 80% */
    }

    #bar90 {
      --cmp: max(0, calc(var(--rssi) - 90));
      --cmp2: min(1, calc(var(--cmp)));
      height: calc(var(--cmp2) * 90%);
      y: calc(100% - var(--cmp2) * 90%);
      fill: #006600; /* Dark Green for 90% */
    }

    #bar100 {
      --cmp: max(0, calc(var(--rssi) - 99));
      --cmp2: min(1, calc(var(--cmp)));
      height: calc(var(--cmp2) * 100%);
      y: calc(100% - var(--cmp2) * 100%);
      fill: #004d00; /* Dark Green for 100% */
    }
   
    .background {
      fill: #cccccc;
    }
   
    rect {
      transition: height 1s ease-in-out, y 1s ease-in-out;
    }
  </style>
 
  <script>
   console.log("los gehts");
    (() => {
        const script = document.currentScript;
        const svg = script.closest('svg');
        const container = svg.parentNode.parentNode;

        function setValueFn(value) {
          console.log("Neuer RSSI-Wert:", value);
          svg.style.setProperty("--rssi", 100 + parseInt(value));
        }

        container.setValueFn = setValueFn;
    })();
  </script>
 
  <rect class="background" x="0" y="99%" width="7%" height="1%" />
  <rect class="background" x="9%" y="90%" width="7%" height="10%" />
  <rect class="background" x="18%" y="80%" width="7%" height="20%" />
  <rect class="background" x="27%" y="70%" width="7%" height="30%" />
  <rect class="background" x="36%" y="60%" width="7%" height="40%" />
  <rect class="background" x="45%" y="50%" width="7%" height="50%" />
  <rect class="background" x="54%" y="40%" width="7%" height="60%" />
  <rect class="background" x="63%" y="30%" width="7%" height="70%" />
  <rect class="background" x="72%" y="20%" width="7%" height="80%" />
  <rect class="background" x="81%" y="10%" width="7%" height="90%" />
  <rect class="background" x="90%" y="0%" width="7%" height="100%" />

  <rect id="bar00" x="0" width="7%" />
  <rect id="bar10" x="9%" width="7%" />
  <rect id="bar20" x="18%" width="7%" />
  <rect id="bar30" x="27%" width="7%" />
  <rect id="bar40" x="36%" width="7%" />
  <rect id="bar50" x="45%" width="7%" />
  <rect id="bar60" x="54%" width="7%" />
  <rect id="bar70" x="63%" width="7%" />
  <rect id="bar80" x="72%" width="7%" />
  <rect id="bar90" x="81%" width="7%" />
  <rect id="bar100" x="90%" width="7%" />
</svg>

Damian

Eigentlich wäre die beste Option den Html-Code in FHEM-Widgets unterzubringen.

Damit es die meisten nutzen, braucht es eines einfachen Aufrufs. Mit regex sind die meisten schon überfordert. Allerdings muss man sich den Aufbau von Widgets erst anschauen.

Das hätte auch den Vorteil, dass man Interaktion über FHEMWEB nutzen könnte. Widgets lassen sich in FHEMWEB gut nutzen. Ich habe mit @Ellert auch in DOIF-uiTable die Option eingebaut FHEM-Widgets zu nutzen.

Die meisten bisherigen Widgets nutzen keine svg-Elemente (bis auf die Icons). Sie sehen recht altbacken aus. Das beste Beispiel ist der Slider, wo man noch nicht mal den Anfang und das Ende erkennen kann.

Das knob-Widget ist ein gutes Beispiel, wie ein fremder SVG-Code in FHEM-Widget integriert wurde.

https://wiki.fhem.de/wiki/FHEMWEB/Widgets
Programmierte FHEM-Module: DOIF-FHEM, DOIF-Perl, DOIF-uiTable, THRESHOLD, FHEM-Befehl: IF

Torxgewinde

Danke @Damian, ich würde gerne noch mit der "nur SVG" Option arbeiten und ich glaube ich habe es nun verstanden:

In der JS-Konsole sehe ich ja, dass die relevanten Daten zur Verfügung stehen. Dort rauschen die geänderten Readings des Raumes den ich gerade betrachte und deren Timestamps mit durch. Das heißt die Daten sind eigentlich bereits im Browser und ich möchte gerne dran kommen.

Relevant scheint mir dir Funktion FW_doUpdate() aus fhemweb.js. Dort wird eine Funktion setValue(d) definiert, die entweder den DOM Knoten austauscht (der else Zweig), oder die JS Funktion setValueFn() aufruft, aber eben nur mit dem Wert zu der jeweiligen informid.

Jetzt kann man, wenn man "fremde" Daten einer anderen informId haben will, einfach neue DOM Knoten anlegen, denen die "fremde" informId verpassen und dann fließen auch die Daten. Das der DOM dann mehrere gleiche informId haben kann, sollte meinem Verständnis nach nicht stören, da setValue() einfach mit einer .each-Funktion arbeitet. Hat mich zwei Stunden gekostet darauf zu kommen, aber so geht es:

bargraph2.svg
<svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" style="--rssi:100">
  <style>
    #bar00 {
      height: 1%;
      y: 99%;
      fill: #ff0000; /* Red for 0% */
    }

    /* For bars from 10% to 100% */
    #bar10 {
      --cmp: max(0, calc(var(--rssi) - 10));
      --cmp2: min(1, calc(var(--cmp)));
      height: calc(var(--cmp2) * 10%);
      y: calc(100% - var(--cmp2) * 10%);
      fill: #ff0000; /* Red for 10% */
    }

    #bar20 {
      --cmp: max(0, calc(var(--rssi) - 20));
      --cmp2: min(1, calc(var(--cmp)));
      height: calc(var(--cmp2) * 20%);
      y: calc(100% - var(--cmp2) * 20%);
      fill: #ff5500; /* Orange for 20% */
    }

    #bar30 {
      --cmp: max(0, calc(var(--rssi) - 30));
      --cmp2: min(1, calc(var(--cmp)));
      height: calc(var(--cmp2) * 30%);
      y: calc(100% - var(--cmp2) * 30%);
      fill: #ff9900; /* Yellow-Orange for 30% */
    }

    #bar40 {
      --cmp: max(0, calc(var(--rssi) - 40));
      --cmp2: min(1, calc(var(--cmp)));
      height: calc(var(--cmp2) * 40%);
      y: calc(100% - var(--cmp2) * 40%);
      fill: #ffcc00; /* Yellow for 40% */
    }

    #bar50 {
      --cmp: max(0, calc(var(--rssi) - 50));
      --cmp2: min(1, calc(var(--cmp)));
      height: calc(var(--cmp2) * 50%);
      y: calc(100% - var(--cmp2) * 50%);
      fill: #ffff00; /* Yellow for 50% */
    }

    #bar60 {
      --cmp: max(0, calc(var(--rssi) - 60));
      --cmp2: min(1, calc(var(--cmp)));
      height: calc(var(--cmp2) * 60%);
      y: calc(100% - var(--cmp2) * 60%);
      fill: #99ff00; /* Light Green for 60% */
    }

    #bar70 {
      --cmp: max(0, calc(var(--rssi) - 70));
      --cmp2: min(1, calc(var(--cmp)));
      height: calc(var(--cmp2) * 70%);
      y: calc(100% - var(--cmp2) * 70%);
      fill: #66cc00; /* Green for 70% */
    }

    #bar80 {
      --cmp: max(0, calc(var(--rssi) - 80));
      --cmp2: min(1, calc(var(--cmp)));
      height: calc(var(--cmp2) * 80%);
      y: calc(100% - var(--cmp2) * 80%);
      fill: #339900; /* Darker Green for 80% */
    }

    #bar90 {
      --cmp: max(0, calc(var(--rssi) - 90));
      --cmp2: min(1, calc(var(--cmp)));
      height: calc(var(--cmp2) * 90%);
      y: calc(100% - var(--cmp2) * 90%);
      fill: #006600; /* Dark Green for 90% */
    }

    #bar100 {
      --cmp: max(0, calc(var(--rssi) - 99));
      --cmp2: min(1, calc(var(--cmp)));
      height: calc(var(--cmp2) * 100%);
      y: calc(100% - var(--cmp2) * 100%);
      fill: #004d00; /* Dark Green for 100% */
    }
   
    .background {
      fill: #cccccc;
    }
   
    rect {
      transition: height 1s ease-in-out, y 1s ease-in-out;
    }
  </style>
 
  <script>
    console.log("los gehts");
    (() => {
      const script = document.currentScript;
      const svg = script.closest('svg');
      const container = svg.parentNode.parentNode;
     
      if (!container || !container.hasAttribute("informId")) {
        console.warn("Container oder informId nicht vorhanden. Funktion wird abgebrochen.");
        return;
      }
     
      const name = container.getAttribute("informId");

      container.setValueFn = (value) => {
        console.warn("Neuer Wert für InformId "+name+": ", value);
        /* svg.style.setProperty("--rssi", 100 + parseInt(value)); */
      };
       
      function addInformIdHandler(informId, setFn) {
        const div = document.createElement('div');
        div.style.display = 'none';
        div.setAttribute('informId', informId);
        div.setValueFn = setFn;
        container.appendChild(div);
      }

      addInformIdHandler(name+"-wert", (value) => {
        console.warn("Neuer wert für informID: "+name+"-wert: ", value);
        svg.style.setProperty("--rssi", 100 + parseInt(value));
      });
     
    })();
  </script>
 
  <rect class="background" x="0" y="99%" width="7%" height="1%" />
  <rect class="background" x="9%" y="90%" width="7%" height="10%" />
  <rect class="background" x="18%" y="80%" width="7%" height="20%" />
  <rect class="background" x="27%" y="70%" width="7%" height="30%" />
  <rect class="background" x="36%" y="60%" width="7%" height="40%" />
  <rect class="background" x="45%" y="50%" width="7%" height="50%" />
  <rect class="background" x="54%" y="40%" width="7%" height="60%" />
  <rect class="background" x="63%" y="30%" width="7%" height="70%" />
  <rect class="background" x="72%" y="20%" width="7%" height="80%" />
  <rect class="background" x="81%" y="10%" width="7%" height="90%" />
  <rect class="background" x="90%" y="0%" width="7%" height="100%" />

  <rect id="bar00" x="0" width="7%" />
  <rect id="bar10" x="9%" width="7%" />
  <rect id="bar20" x="18%" width="7%" />
  <rect id="bar30" x="27%" width="7%" />
  <rect id="bar40" x="36%" width="7%" />
  <rect id="bar50" x="45%" width="7%" />
  <rect id="bar60" x="54%" width="7%" />
  <rect id="bar70" x="63%" width="7%" />
  <rect id="bar80" x="72%" width="7%" />
  <rect id="bar90" x="81%" width="7%" />
  <rect id="bar100" x="90%" width="7%" />
</svg>

Das Testdevice wird damit wieder einfacher:
defmod bargraphSVG dummy
attr bargraphSVG devStateIcon {\
    my $val = 100+ReadingsVal($name, 'wert', '???');;\
    my $bargraph = FW_makeImage('bargraph2');;\
    $bargraph =~ s/(<svg[^>]+style="[^"]*)--rssi:\d{1,3}/$1--rssi:$val/;;\
    \
    return $bargraph;;\
}
attr bargraphSVG readingList wert
attr bargraphSVG setList wert
attr bargraphSVG webCmd wert -100:wert -82:wert -55:wert -40:wert -10:wert -3:wert 0

So habe ich die Daten in der Hand, was mir noch fehlt ist, dass das Reading "wert" damit hardgecoded ist. Da denke ich auch nochmal drüber nach...





Torxgewinde

Man kann mit Platzhaltern arbeiten und diese dann mit den Initialwerten aller Readings des Device ersetzen. Wenn man auf Readingsupdates reagieren möchte ergänzt man entsprechende JS Funktionen in dem SVG. Es muss dort ja ohnehin definiert werden, was mit den Daten gemacht werden soll, da kann man auch den Readingnamen festlegen:

bargraph3.svg
<svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" style="--rssi:100">
  <!-- Readings-Placeholders: state: %state%, rssi: %rssi% -->
  <style>
    #bar00 {
      height: 1%;
      y: 99%;
      fill: #ff0000;
    }

    /* For bars from 10% to 100% */
    #bar10 {
      --cmp: max(0, calc(var(--rssi) - 10));
      --cmp2: min(1, calc(var(--cmp)));
      height: calc(var(--cmp2) * 10%);
      y: calc(100% - var(--cmp2) * 10%);
      fill: #ff0000;
    }

    #bar20 {
      --cmp: max(0, calc(var(--rssi) - 20));
      --cmp2: min(1, calc(var(--cmp)));
      height: calc(var(--cmp2) * 20%);
      y: calc(100% - var(--cmp2) * 20%);
      fill: #ff5500;
    }

    #bar30 {
      --cmp: max(0, calc(var(--rssi) - 30));
      --cmp2: min(1, calc(var(--cmp)));
      height: calc(var(--cmp2) * 30%);
      y: calc(100% - var(--cmp2) * 30%);
      fill: #ff9900;
    }

    #bar40 {
      --cmp: max(0, calc(var(--rssi) - 40));
      --cmp2: min(1, calc(var(--cmp)));
      height: calc(var(--cmp2) * 40%);
      y: calc(100% - var(--cmp2) * 40%);
      fill: #ffcc00;
    }

    #bar50 {
      --cmp: max(0, calc(var(--rssi) - 50));
      --cmp2: min(1, calc(var(--cmp)));
      height: calc(var(--cmp2) * 50%);
      y: calc(100% - var(--cmp2) * 50%);
      fill: #ffff00;
    }

    #bar60 {
      --cmp: max(0, calc(var(--rssi) - 60));
      --cmp2: min(1, calc(var(--cmp)));
      height: calc(var(--cmp2) * 60%);
      y: calc(100% - var(--cmp2) * 60%);
      fill: #99ff00;
    }

    #bar70 {
      --cmp: max(0, calc(var(--rssi) - 70));
      --cmp2: min(1, calc(var(--cmp)));
      height: calc(var(--cmp2) * 70%);
      y: calc(100% - var(--cmp2) * 70%);
      fill: #66cc00;
    }

    #bar80 {
      --cmp: max(0, calc(var(--rssi) - 80));
      --cmp2: min(1, calc(var(--cmp)));
      height: calc(var(--cmp2) * 80%);
      y: calc(100% - var(--cmp2) * 80%);
      fill: #339900;
    }

    #bar90 {
      --cmp: max(0, calc(var(--rssi) - 90));
      --cmp2: min(1, calc(var(--cmp)));
      height: calc(var(--cmp2) * 90%);
      y: calc(100% - var(--cmp2) * 90%);
      fill: #006600;
    }

    #bar100 {
      --cmp: max(0, calc(var(--rssi) - 99));
      --cmp2: min(1, calc(var(--cmp)));
      height: calc(var(--cmp2) * 100%);
      y: calc(100% - var(--cmp2) * 100%);
      fill: #004d00;
    }
   
    .background {
      fill: #cccccc;
    }
   
    rect {
      transition: height 1s ease-in-out, y 1s ease-in-out;
    }
  </style>
 
  <script>

    (() => {
      const script = document.currentScript;
      const svg = script.closest('svg');
      const container = svg.parentNode.parentNode;
     
      if (!container || !container.hasAttribute("informId")) {
        console.warn("Container or informId not found.");
        return;
      }
     
      const name = container.getAttribute("informId");

      container.setValueFn = (value) => {
        /* This is typically for the Reading "state" */
        console.log("New value for InformId "+name+": ", value);
        const el = svg.querySelector(".infotext");
        if (el) el.textContent = value;
      };
       
      function addInformIdHandler(informId, setFn) {
        const div = document.createElement('div');
        div.style.display = 'none';
        div.setAttribute('informId', informId);
        div.setValueFn = setFn;
        container.appendChild(div);
      }
     
      /* here we handle the details for Reading "rssi" */
      addInformIdHandler(name+"-rssi", (value) => { 
        console.log("New value for informID: "+name+"-rssi: ", value);
        svg.style.setProperty("--rssi", 100 + parseInt(value));
      });
     
    })();
  </script>
 
  <rect class="background" x="0" y="99%" width="7%" height="1%" />
  <rect class="background" x="9%" y="90%" width="7%" height="10%" />
  <rect class="background" x="18%" y="80%" width="7%" height="20%" />
  <rect class="background" x="27%" y="70%" width="7%" height="30%" />
  <rect class="background" x="36%" y="60%" width="7%" height="40%" />
  <rect class="background" x="45%" y="50%" width="7%" height="50%" />
  <rect class="background" x="54%" y="40%" width="7%" height="60%" />
  <rect class="background" x="63%" y="30%" width="7%" height="70%" />
  <rect class="background" x="72%" y="20%" width="7%" height="80%" />
  <rect class="background" x="81%" y="10%" width="7%" height="90%" />
  <rect class="background" x="90%" y="0%" width="7%" height="100%" />

  <rect id="bar00" x="0" width="7%" />
  <rect id="bar10" x="9%" width="7%" />
  <rect id="bar20" x="18%" width="7%" />
  <rect id="bar30" x="27%" width="7%" />
  <rect id="bar40" x="36%" width="7%" />
  <rect id="bar50" x="45%" width="7%" />
  <rect id="bar60" x="54%" width="7%" />
  <rect id="bar70" x="63%" width="7%" />
  <rect id="bar80" x="72%" width="7%" />
  <rect id="bar90" x="81%" width="7%" />
  <rect id="bar100" x="90%" width="7%" />
 
  <text class="infotext" x="1" y="30" font-family="Arial" font-size="30">%state%</text>
</svg>

Und ein einfaches Testdevice dazu:
defmod bargraphSVG dummy
attr bargraphSVG devStateIcon {\
my $val = 100+ReadingsVal($name, 'rssi', '???');;\
my $bargraph = FW_makeImage('bargraph3');;\
$bargraph =~ s/(<svg[^>]+style="[^"]*)--rssi:\d{1,3}/$1--rssi:$val/;;\
\
foreach my $reading (keys %{$defs{$name}->{READINGS}}) {\
my $value = $defs{$name}->{READINGS}{$reading}{VAL} // '';;\
$bargraph =~ s/%\Q$reading\E%/$value/g;;\
}\
\
return $bargraph;;\
}
attr bargraphSVG readingList rssi
attr bargraphSVG setList rssi
attr bargraphSVG userReadings state {\
return ReadingsVal($name, 'rssi', 0);;\
}
attr bargraphSVG webCmd rssi -100:rssi -82:rssi -55:rssi -40:rssi -10:rssi -3:rssi 0

Taipan72

Hi,
ich habe das mal übernommen für ein Homematic-Device um RSSI-Werte zu visualisieren und bekomme bei Schaltvorgängen immer:

Arbeitszimmer line 1: Uncaught TypeError: Cannot read properties of null (reading 'closest')

{
my $val = 100+ReadingsVal($name, 'rssipeer', '???');;
my $bargraph = FW_makeImage('bargraph3');;
$bargraph =~ s/(<svg[^>]+style="[^"]*)--rssi:\d{1,3}/$1--rssi:$val/;;
foreach my $reading (keys %{$defs{$name}->{READINGS}}) {
my $value = $defs{$name}->{READINGS}{$reading}{VAL} // '';;
$bargraph =~ s/%\Q$reading\E%/$value/g;;
}
return $bargraph;;
}

Funktionieren tut es aber eigentlich wie es soll und die Anzeige ist auch korrekt!

Torxgewinde

Taipan72: Ich muss ein wenig raten:
Die Meldung ist zu deinem Device in FHEM/Perl, korrekt? Du hast den Code als Attribut "devStateIcon" eingebunden?

Hast du überhaupt ein Reading "closest" in deinem Device?

Testweise, wenn du eh nur die beiden Readings state und rssipeer übergeben willst, kannst du den Code mal testweise ändern auf (ich habe es nicht in der Formatierung für die fhem.cfg, sondern zum besserem Lesen formatiert, bitte beachten!):

attr deinDeviceName devStateIcon {
  my $val = 100 + ReadingsNum($name, 'rssipeer', 0);
  my $mstate = ReadingsVal($name, 'state', '???');
 
  my $bargraph = FW_makeImage('bargraph3');
 
  $bargraph =~ s/(<svg[^>]+style="[^"]*)--rssi:\d{1,3}/$1--rssi:$val/;
  $bargraph =~ s/%state%/$mstate/g;
  $bargraph =~ s/%rssi%/$val/g;
 
  return $bargraph;
}

Taipan72

Oh,
ich hatte gedacht ich könnte es einfach "zweckentfremden". In den Homematic-Devices gibt es hier natürlich kein entsprechendes Reading...
Wäre es denn möglich das SVG und den devStateIcon-Eintrag allgemeingültig für alle Devices mit RSSI-Wert zu machen?

Torxgewinde

Klar,
Entweder könnte man in dem SVG den String von "rssi" auf dein Reading "rssipeer" ändern,
oder du machst dir einfach ein Reading das "rssi" heißt, indem du ein UserReading an deinem Device hinzufügst. Das macht nichts besonderes außer den Wert von "rssipeer" auf das zusätzliche Reading "rssi" zu kopieren:

Ungeprüft runtergeschrieben, bitte ggf. korrigieren:
attr deinDeviceName userReadings rssi:rssipeer:.* { return ReadingsNum($name, 'rssipeer', 0); }

Ich würde das userReading als Erstes ausprobieren, das ist am schnellsten gemacht.

Taipan72

Die Fehlermeldung bleibt aber!

fhem?detail=HML_Arbeitszimmer_LED&fw_id= line 1:
Uncaught TypeError: Cannot read properties of null (reading 'closest')

Torxgewinde

Das müsste dann ein Javascript Fehler sein.

In deinem Fall würde ich vorschlagen auf die Animation zu verzichten und dafür ein "normales" SVG, also eins ganz ohne JS zu nehmen. Wo kein JS Script ist, kann dann auch kein JS-Fehler auftauchen.

Versuche also bitte mal dieses Icon bargraph.svg, anstelle von bargraph3.svg:
<svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" style="--rssi:100">
  <style>
    #bar00 {
      height: 1%;
      y: 99%;
      fill: #ff0000; /* Red for 0% */
    }

    /* For bars from 10% to 100% */
    #bar10 {
      --cmp: max(0, calc(var(--rssi) - 10));
      --cmp2: min(1, calc(var(--cmp)));
      height: calc(var(--cmp2) * 10%);
      y: calc(100% - var(--cmp2) * 10%);
      fill: #ff0000; /* Red for 10% */
    }

    #bar20 {
      --cmp: max(0, calc(var(--rssi) - 20));
      --cmp2: min(1, calc(var(--cmp)));
      height: calc(var(--cmp2) * 20%);
      y: calc(100% - var(--cmp2) * 20%);
      fill: #ff5500; /* Orange for 20% */
    }

    #bar30 {
      --cmp: max(0, calc(var(--rssi) - 30));
      --cmp2: min(1, calc(var(--cmp)));
      height: calc(var(--cmp2) * 30%);
      y: calc(100% - var(--cmp2) * 30%);
      fill: #ff9900; /* Yellow-Orange for 30% */
    }

    #bar40 {
      --cmp: max(0, calc(var(--rssi) - 40));
      --cmp2: min(1, calc(var(--cmp)));
      height: calc(var(--cmp2) * 40%);
      y: calc(100% - var(--cmp2) * 40%);
      fill: #ffcc00; /* Yellow for 40% */
    }

    #bar50 {
      --cmp: max(0, calc(var(--rssi) - 50));
      --cmp2: min(1, calc(var(--cmp)));
      height: calc(var(--cmp2) * 50%);
      y: calc(100% - var(--cmp2) * 50%);
      fill: #ffff00; /* Yellow for 50% */
    }

    #bar60 {
      --cmp: max(0, calc(var(--rssi) - 60));
      --cmp2: min(1, calc(var(--cmp)));
      height: calc(var(--cmp2) * 60%);
      y: calc(100% - var(--cmp2) * 60%);
      fill: #99ff00; /* Light Green for 60% */
    }

    #bar70 {
      --cmp: max(0, calc(var(--rssi) - 70));
      --cmp2: min(1, calc(var(--cmp)));
      height: calc(var(--cmp2) * 70%);
      y: calc(100% - var(--cmp2) * 70%);
      fill: #66cc00; /* Green for 70% */
    }

    #bar80 {
      --cmp: max(0, calc(var(--rssi) - 80));
      --cmp2: min(1, calc(var(--cmp)));
      height: calc(var(--cmp2) * 80%);
      y: calc(100% - var(--cmp2) * 80%);
      fill: #339900; /* Darker Green for 80% */
    }

    #bar90 {
      --cmp: max(0, calc(var(--rssi) - 90));
      --cmp2: min(1, calc(var(--cmp)));
      height: calc(var(--cmp2) * 90%);
      y: calc(100% - var(--cmp2) * 90%);
      fill: #006600; /* Dark Green for 90% */
    }

    #bar100 {
      --cmp: max(0, calc(var(--rssi) - 99));
      --cmp2: min(1, calc(var(--cmp)));
      height: calc(var(--cmp2) * 100%);
      y: calc(100% - var(--cmp2) * 100%);
      fill: #004d00; /* Dark Green for 100% */
    }
   
    .background {
      fill: #cccccc;
    }

  </style>
 
  <rect class="background" x="0" y="99%" width="7%" height="1%" />
  <rect class="background" x="9%" y="90%" width="7%" height="10%" />
  <rect class="background" x="18%" y="80%" width="7%" height="20%" />
  <rect class="background" x="27%" y="70%" width="7%" height="30%" />
  <rect class="background" x="36%" y="60%" width="7%" height="40%" />
  <rect class="background" x="45%" y="50%" width="7%" height="50%" />
  <rect class="background" x="54%" y="40%" width="7%" height="60%" />
  <rect class="background" x="63%" y="30%" width="7%" height="70%" />
  <rect class="background" x="72%" y="20%" width="7%" height="80%" />
  <rect class="background" x="81%" y="10%" width="7%" height="90%" />
  <rect class="background" x="90%" y="0%" width="7%" height="100%" />

  <rect id="bar00" x="0" width="7%" />
  <rect id="bar10" x="9%" width="7%" />
  <rect id="bar20" x="18%" width="7%" />
  <rect id="bar30" x="27%" width="7%" />
  <rect id="bar40" x="36%" width="7%" />
  <rect id="bar50" x="45%" width="7%" />
  <rect id="bar60" x="54%" width="7%" />
  <rect id="bar70" x="63%" width="7%" />
  <rect id="bar80" x="72%" width="7%" />
  <rect id="bar90" x="81%" width="7%" />
  <rect id="bar100" x="90%" width="7%" />
 
  <text class="infotext" x="1" y="30" font-size="30">%rssi%</text>
</svg>

Zum Einbinden als pures CSS devStateIcon gänzlich ohne JavaScript:
attr deinDeviceName devStateIcon {
  my $val = ReadingsNum($name, 'rssipeer', 0);
  my $val2 = 100 + $val;
 
  my $bargraph = FW_makeImage('bargraph');
  $bargraph =~ s/(<svg[^>]+style="[^"]*)--rssi:\d{1,3}/$1--rssi:$val2/;
  $bargraph =~ s/%rssi%/$val/g;
 
  return $bargraph;
}

Taipan72

Also ich nutze den Skin von "Hausautomtisierung.com" (der Fehler ist in allen Skins) und als Browser Chrome. Und das Fehlerfenster kommt in der WEB instanz von FHEM beim an-/ausschalten des Aktors... Die Funktionen sind aber gegeben

Torxgewinde

Ok, das wird vermutlich zu einer unerwarteten Komplikation geführt haben. Probier mal bitte die obige SVG, die ohne JS auskommt. Wo kein JS drin ist, sollten dann auch die "normalen" FHEM Features in den diversen Skins greifen. Das einzige was du verlierst ist eine weiche Animation der Balken, das ist IMHO ganz niedrige Prio in dieser Anwendung.

Ich meine dieses SVG aus diesem Post: https://forum.fhem.de/index.php?topic=141113.msg1337776#msg1337776

Ich sehe gerade, zum Einbinden sollte es so sein (habe es oben auch angepasst, ich hatte vergessen %rssi% zu ersetzen"):
attr deinDeviceName devStateIcon {
  my $val = ReadingsNum($name, 'rssipeer', 0);
  my $val2 = 100 + $val;
 
  my $bargraph = FW_makeImage('bargraph');
  $bargraph =~ s/(<svg[^>]+style="[^"]*)--rssi:\d{1,3}/$1--rssi:$val2/;
  $bargraph =~ s/%rssi%/$val/g;
 
  return $bargraph;
}