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

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

Vorheriges Thema - Nächstes Thema

Torxgewinde

Hallo,
Ich wollte für einen Tasmota-Shelly einen einfachen RSSI-Indicator im Status haben, der sieht dann so ungefähr aus:

  •  ▂ ▃ ▄ für RSSI Werte von -100 bis -90
  • ▁ ▂ ▃ ▄ für RSSI Werte von -89 bis -70
  • ▁ ▂ ▃ ▄ für RSSI Werte von -69 bis -50
  • ▁ ▂ ▃ ▄ für RSSI Werte von -49 bis 0
  • Andere Werte erzeugen ? ? ? als Wert

    Hier der Snippet für ein Stateformat:
    attr Terrassenlicht stateFormat { \
      return 'offline' if (ReadingsVal($name, "availability", "") eq "offline");;\
      \
      my %map;;\
      $map{$_} = '<span style="color:red;;">▁</span><span style="color:#A8A8A8;;">▂&ThinSpace;;▃&ThinSpace;;▄</span>' for -100..-90;;\
      $map{$_} = '<span style="color:orange;;">▁&ThinSpace;;▂</span><span style="color:#A8A8A8;;">&ThinSpace;;▃&ThinSpace;;▄</span>' for -89..-70;;\
      $map{$_} = '<span style="color:yellow;;">▁&ThinSpace;;▂&ThinSpace;;▃</span><span style="color:#A8A8A8;;">&ThinSpace;;▄</span>' for -69..-50;;\
      $map{$_} = '<span style="color:green;;">▁&ThinSpace;;▂&ThinSpace;;▃&ThinSpace;;▄</span>' for -49..0;;\
      \
      return '<html>'.\
        ReadingsVal($name, "state", "???").\
        ' &nbsp;;'.\
        ($map{ReadingsNum($name, "Wifi_Signal", 0)} // '???').\
        '</html>';;\
    }

    Sie sieht es dann aus:
    Du darfst diesen Dateianhang nicht ansehen.

Wernieman

Würde es nicht Sinn machen, es in eine eigene Prozedure auszulagern?

Es ist so direkt aus der Config kopiert?
- Bitte um Input für Output
- When there is a Shell, there is a Way
- Wann war Dein letztes Backup?

Wie man Fragen stellt: https://tty1.net/smart-questions_de.html

JoWiemann

Hallo,

ich würde:
ReadingsVal($name, "availability", "");
durch:
ReadingsVal($name, "availability", "offline");

Grüße Jörg
Jörg Wiemann

Slave: RPi B+ mit 512 MB, COC (868 MHz), CUL V3 (433.92MHz SlowRF); FHEMduino, Aktuelles FHEM

Master: CubieTruck; Debian; Aktuelles FHEM

Torxgewinde

#3
Hallo @Wernieman: Sofern man das bei mehreren Devices wiederverwendet kann/sollte man über Auslagern nachdenken. Ich favorisiere die Dinge, die das Device betreffen, beim Device zu lassen. Dann ist alles schön auf einer Seite. Gehen tut beides, da gibt FHEM einem keine starren Regeln vor. Ja, das ist so aus der Config kopiert, aber eben nur der Snippet für attr stateFormat.

Ich arbeite sogar lieber mit userReadings um mir irgendwelche trivialen Notifies oder DOIFs zu sparen (weniger Devices). Hier ein Beispiel einer Tasters, der direkt beim Taster-Device eingetragen ist und den Aktor so ansteuert. Nebenbei mappt der Ausdruck von '0','1' auf 'off','on' und setzt den State basierend auf dem Reading switchState:
attr TasterTerrassenbeleuchtung userReadings state:switchState:.* {
    my $ss = ReadingsVal($name, "switchState", "???");
    my $val = ({ 0 => 'off', 1 => 'on' }->{$ss} // $ss);
    fhem("set Terrassenlicht $val");
    return $val;
}


Hallo @JoWiemann: Danke, guter Hinweis.
attr Terrassenlicht stateFormat { \
  return 'offline' if (ReadingsVal($name, 'availability', 'offline') eq 'offline');;\
  \
  my %map;;\
  $map{$_} = '<span style="color:red;;">▁</span><span style="color:#A8A8A8;;">▂&ThinSpace;;▃&ThinSpace;;▄</span>' for -100..-90;;\
  $map{$_} = '<span style="color:orange;;">▁&ThinSpace;;▂</span><span style="color:#A8A8A8;;">&ThinSpace;;▃&ThinSpace;;▄</span>' for -89..-70;;\
  $map{$_} = '<span style="color:yellow;;">▁&ThinSpace;;▂&ThinSpace;;▃</span><span style="color:#A8A8A8;;">&ThinSpace;;▄</span>' for -69..-50;;\
  $map{$_} = '<span style="color:green;;">▁&ThinSpace;;▂&ThinSpace;;▃&ThinSpace;;▄</span>' for -49..0;;\
  \
  return '<html>'.\
    ReadingsVal($name, "state", "???").\
    ' &nbsp;;'.\
    ($map{ReadingsNum($name, "Wifi_Signal", 0)} // '???').\
    '</html>';;\
}


Torxgewinde

#4
Btw: Falls jemand mehr Zwischenstufen möchte: hier wäre ein Balken von ganz klein bis ganz groß:

▁ ▂ ▃ ▄ ▅ ▆ ▇ █

Dies hier wäre eine Variante mit feinerer Abstufung dazu:

attr Terrassenlicht stateFormat { \
  return 'offline' if (ReadingsVal($name, "availability", "offline") eq "offline");;\
  \
  my %map;;\
  $map{$_} = '<span style="color:red;;">▁</span>' for -100..-90;;\
  $map{$_} = '<span style="color:darkorange;;">▁&ThinSpace;;▂</span>' for -89..-80;;\
  $map{$_} = '<span style="color:orange;;">▁&ThinSpace;;▂&ThinSpace;;▃</span>' for -79..-70;;\
  $map{$_} = '<span style="color:yellow;;">▁&ThinSpace;;▂&ThinSpace;;▃&ThinSpace;;▄</span>' for -69..-60;;\
  $map{$_} = '<span style="color:lightgreen;;">▁&ThinSpace;;▂&ThinSpace;;▃&ThinSpace;;▄&ThinSpace;;▅</span>' for -59..-50;;\
  $map{$_} = '<span style="color:green;;">▁&ThinSpace;;▂&ThinSpace;;▃&ThinSpace;;▄&ThinSpace;;▅&ThinSpace;;▆</span>' for -49..-40;;\
  $map{$_} = '<span style="color:darkgreen;;">▁&ThinSpace;;▂&ThinSpace;;▃&ThinSpace;;▄&ThinSpace;;▅&ThinSpace;;▆&ThinSpace;;▇</span>' for -39..-30;;\
  $map{$_} = '<span style="color:limegreen;;">▁&ThinSpace;;▂&ThinSpace;;▃&ThinSpace;;▄&ThinSpace;;▅&ThinSpace;;▆&ThinSpace;;▇&ThinSpace;;█</span>' for -29..0;;\
\
  return '<html>'.\
    ReadingsVal($name, "state", "???").\
    ' <span style="position: relative;; display: inline-block;; transform: scaleX(0.4);;">'.\
    '   <span style="color: lightgray;; font-size: 1em;; font-weight: bold;; white-space: nowrap;;">▁&ThinSpace;;▂&ThinSpace;;▃&ThinSpace;;▄&ThinSpace;;▅&ThinSpace;;▆&ThinSpace;;▇&ThinSpace;;█</span>'.\
    '   <span style="position: absolute;; top: 0;; left: 0;; font-size: 1em;; font-weight: bold;; white-space: nowrap;;">'.\
    ($map{ReadingsNum($name, "Wifi_Signal", 0)} // '???').\
    '   </span>'.\
    '</span></html>';;\
}

So sieht es dann zB. auf dem Handy aus:
Du darfst diesen Dateianhang nicht ansehen.

Beta-User

Zitat von: Torxgewinde am 17 März 2025, 06:30:31aber eben nur der Snippet für attr stateFormat.
Wenn du eine grafische Anzeige generieren willst, ist stateFormat/STATE imo nicht die optimale Stelle => devStateIcon... (stateFormat wird immer evaluiert, wenn sich irgendein Reading ändert, devStateIcon wird nur ausgeführt, wenn man eine Anzeige braucht.)

Davon abgesehen würde ich solche "wiederverwertbaren" snipplets (die man ggf. ja auch mit anderem Zeug dann kombiniert) immer in myUtils-code auszulagern, statt ständig alles (im Ergebnis) durch Text-eval zu hauen.

Dass es keine verbindlichen Vorgaben für beides gibt, ist eine Sache, aber effizient macht es das noch lange nicht, wenn man die hier vorgeschlagene Variante wählt..

Zitat von: Torxgewinde am 17 März 2025, 06:30:31arbeite sogar lieber mit userReadings um mir irgendwelche trivialen Notifies oder DOIFs zu sparen (weniger Devices)
Irgendwie habe ich kein besonders gutes Gefühl dabei, innerhalb der Event-Verarbeitung plötzlich ganz andere Devices zu adressieren, und "weniger Devices" sehe ich auch nicht unbedingt als Wert an sich an.
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

Von der Effizienz her ist es etwas suboptimal, denn bei jedem Event, wird ein Hash mit 100 Elementen aufgebaut, obwohl man immer nur eins benötigt.
 
Programmierte FHEM-Module: DOIF-FHEM, DOIF-Perl, DOIF-uiTable, THRESHOLD, FHEM-Befehl: IF

Torxgewinde

Auch gute Anregungen, ändert jetzt nichts großartig am Output, ist aber im Detail ein wenig effizienter:

attr Terrassenlicht stateFormat { \
  return 'offline' if (ReadingsVal($name, "availability", "offline") eq "offline");;\
  \
  no warnings 'redefine';;\
  ######\
  #  returns html for a simple bargraph\
  ## parameter $1 is the signal strength RSSI (-100 to 0)\
  ## parameter $2 is an optional color for the bar\
  sub SignalToBar($;;$) {\
    my ($v, $c) = @_;;\
    \
    return '<span style="color:'.($c // 'red')       .';;">▁</span>' if $v >= -100 && $v <= -90;;\
    return '<span style="color:'.($c // 'darkorange').';;">▁&ThinSpace;;▂</span>' if $v >= -89 && $v <= -80;;\
    return '<span style="color:'.($c // 'orange')    .';;">▁&ThinSpace;;▂&ThinSpace;;▃</span>' if $v >= -79 && $v <= -70;;\
    return '<span style="color:'.($c // 'yellow')    .';;">▁&ThinSpace;;▂&ThinSpace;;▃&ThinSpace;;▄</span>' if $v >= -69 && $v <= -60;;\
    return '<span style="color:'.($c // 'lightgreen').';;">▁&ThinSpace;;▂&ThinSpace;;▃&ThinSpace;;▄&ThinSpace;;▅</span>' if $v >= -59 && $v <= -50;;\
    return '<span style="color:'.($c // 'green')     .';;">▁&ThinSpace;;▂&ThinSpace;;▃&ThinSpace;;▄&ThinSpace;;▅&ThinSpace;;▆</span>' if $v >= -49 && $v <= -40;;\
    return '<span style="color:'.($c // 'darkgreen') .';;">▁&ThinSpace;;▂&ThinSpace;;▃&ThinSpace;;▄&ThinSpace;;▅&ThinSpace;;▆&ThinSpace;;▇</span>' if $v >= -39 && $v <= -30;;\
    return '<span style="color:'.($c // 'limegreen') .';;">▁&ThinSpace;;▂&ThinSpace;;▃&ThinSpace;;▄&ThinSpace;;▅&ThinSpace;;▆&ThinSpace;;▇&ThinSpace;;█</span>' if $v >= -29 && $v <= 0;;\
    \
    return '???';;\
  }\
  use warnings 'redefine';;\
  \
  my $signal = ReadingsNum($name, "Wifi_Signal", 0);;\
  my $sigBar = SignalToBar($signal);;\
  my $backGnd = SignalToBar(0, 'lightgray');; #returns a full gray bargraph\
  \
  return '<html>'.\
    ReadingsVal($name, "state", "???").\
    ' <span style="position: relative;; display: inline-block;; transform: scaleX(0.4);; font-size: 1em;; white-space: nowrap;;">'.\
    '   <span style="color: lightgray;;">'.\
    $backGnd.\
    '   </span>'.\
    '   <span style="position: absolute;; top: 0;; left: 0;;">'.\
    $sigBar.\
    '   </span>'.\
    '</span></html>';;\
}

Torxgewinde

...ich guck' mir mal an wie FHEM das mit SVGs umgesetzt, so richtig Browserunabhängig ist die Lösung mit den UTF8 Zeichen nicht und ich will nicht die Schriftarten vorgeben.

Damian

Zitat von: Torxgewinde am 20 März 2025, 07:17:25...ich guck' mir mal an wie FHEM das mit SVGs umgesetzt, so richtig Browserunabhängig ist die Lösung mit den UTF8 Zeichen nicht und ich will nicht die Schriftarten vorgeben.

Im DOIF-Modul habe ich einige SVG-Grafiken als Perlfunktion gebaut, die man durch Aufruf zur Darstellung nutzen kann. Die kannst du dir mal anschauen. Da wird ganz primitiv HTML-Code generiert.
Programmierte FHEM-Module: DOIF-FHEM, DOIF-Perl, DOIF-uiTable, THRESHOLD, FHEM-Befehl: IF

Torxgewinde

Danke Damian, deine DOIF uiTable Komponenten finde ich super und die nutze auch bereits sehr gerne.

In diesem Fall wollte ich es minimal halten. Ich habe hier mal eine Idee umgesetzt, die nur mit einem SVG mit ein wenig cleverem CSS-rules auskommt.

Man braucht dafür ein SVG "bargraph.svg", welches mit dem Parameter --rssi gesteuert werden kann. Das SVG kann man in /opt/fhem/www/images/fhemSVG/bargraph.svg kopieren und FHEM dann einmal neustarten:
<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% */
    }

  </style>

  <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>

Hier ein simples Testdevice um es zu demonstrieren:
defmod bargraphSVG dummy
attr bargraphSVG devStateIcon {\
    my $val = 100+ReadingsVal($name, 'wert', '???');;\
    my $bargraph = FW_makeImage('bargraph');;\
    $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

So sieht es dann aus:
Du darfst diesen Dateianhang nicht ansehen.

Die Funktion FW_makeImage() erlaubt es fill, stroke, data-txt und CSS-Klassen zu setzen. Leider eignet sich davon nichts für CSS-Variablen, deswegen habe ich das mit dem Regex in das SVG geschrieben.

Das relativ ungewühnliche ist, dass man mit den CSS-styles und den Min(),Max() und Calc() Funktionen Elemente ein aus ausblenden kann. Das habe ich bisher noch nicht so gesehen, vielleicht findet der ein oder andere das ja auch auch inspirierend.

Torxgewinde

@Beta-User:
Als DevStateIcon wäre das dann mit dem SVG so ganz OK, danke für den Hinweis:

attr Terrassenlicht devStateIcon {\
  ##on:on off:off offline:light_question\
  return FW_makeImage('light_question') if (ReadingsVal($name, "availability", "offline") eq "offline");;\
  \
  my $val = 100+ReadingsNum($name, 'Wifi_Signal', 0);;\
  my $bargraph = FW_makeImage('progressbar');;\
  $bargraph =~ s/(<svg[^>]+style="[^"]*)--rssi:\d{1,3}/$1--rssi:$val;; border: solid 1px #CCCCCC/;;\
  \
  return '<span>'.ReadingsVal($name, "state", "???")."</span>&nbsp;;".$bargraph;;\
}

So sieht es dann aus, hier habe ich noch einen grauen Rahmen um das SVG ergänzt:
Du darfst diesen Dateianhang nicht ansehen.

Alternativ, wenn man lieber Icons für den Status mag:
attr Terrassenlicht devStateIcon {\
  ##on:on off:off offline:light_question\
  return FW_makeImage('light_question') if (ReadingsVal($name, "availability", "offline") eq "offline");;\
  \
  my $val = 100+ReadingsNum($name, 'Wifi_Signal', 0);;\
  my $bargraph = FW_makeImage('progressbar');;\
  $bargraph =~ s/(<svg[^>]+style="[^"]*)--rssi:\d{1,3}/$1--rssi:$val;; border: solid 1px #CCCCCC/;;\
  \
  my $stateIco = FW_makeImage(ReadingsVal($name, "state", "???"));;\
  \
  return $stateIco."&nbsp;;".$bargraph;;\
}

Sieht dann so aus:
Du darfst diesen Dateianhang nicht ansehen.

Beta-User

Zitat von: Torxgewinde am 20 März 2025, 22:16:24Als DevStateIcon wäre das dann mit dem SVG so ganz OK, danke für den Hinweis:
Immer wieder gerne :) .

Meine persönliches styling wäre farblos, ohne Rand und ohne webCmd (dafür das toggeln über devStateIcon).

Das mit dem SVG finde ich eine coole Lösung, mache ich auch immer mal wieder, irgendwo müßte ich z.B. auch Code für die Visualisierung der Temperaturen im Pufferspeicher gepostet haben.

PS: Falls jemand eine Idee hat, wie man per SVG einen Pfeil bastelt, der sich (entsprechend der Windrichtung) um den Mittelpunkt des SVG dreht (und das ganze über der "normalen" Windrose veranstaltet) - her damit :) . (Der Pfeil aus dem Barometer-SVG ist eigentlich ganz cool, aber bisher ist es mir nicht gelungen, den so zu drehen, dass es vernünftig aussieht...)
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

Torxgewinde

@Beta-User: Der Pfeil den du gerne da rausholen würdest scheint der Pfad M1865 zu sein, der hat einen komisches Zentrum um ihn zu rotieren, aber durch rumprobieren könnte man das barometer.svg so interaktiv machen:
<svg class="icon weather_barometric_pressure" data-txt="weather_barometric_pressure" version="1.0" xmlns="http://www.w3.org/2000/svg" width="468pt" height="470pt" viewBox="0 0 468 470" preserveAspectRatio="xMidYMid meet" style="--myRot: 0deg;">
  <metadata> Created by potrace 1.8, written by Peter Selinger 2001-2007 </metadata>
  <g transform="translate(0,470) scale(0.158108,-0.158108)" stroke="none">
    <path d="M702 2958 c-6 -9 -6 -71 2 -188 11 -173 11 -175 35 -178 l24 -4 -8 184 c-7 171 -12 197 -36 198 -4 0 -11 -6 -17 -12z"></path>
    <path d="M887 2963 c-3 -4 -6 -62 -8 -128 -4 -117 -3 -120 18 -123 20 -3 22 2 28 70 10 110 4 182 -17 186 -9 2 -18 -1 -21 -5z"></path>
    <path d="M1057 2906 c-17 -118 -19 -203 -7 -211 21 -13 33 12 46 102 20 133 19 153 -10 153 -20 0 -24 -6 -29 -44z"></path>
    <path d="M1230 2863 c-7 -32 -18 -86 -25 -120 -13 -66 -12 -73 16 -73 13 0 21 19 37 88 38 164 38 162 9 162 -23 0 -27 -6 -37 -57z"></path>
    <path d="M1366 2710 c-46 -161 -51 -207 -24 -198 15 5 121 336 114 355 -3 7 -14 13 -24 13 -15 0 -26 -29 -66 -170z"></path>
    <path d="M1575 2808 c-2 -7 -20 -55 -40 -106 -19 -52 -35 -100 -35 -108 0 -17 36 -19 43 -1 59 141 86 211 81 218 -8 13 -44 11 -49 -3z"></path>
    <path d="M1689 2643 c-48 -103 -54 -133 -27 -133 16 0 118 189 118 219 0 15 -6 21 -20 21 -17 0 -31 -22 -71 -107z"></path>
    <path d="M1837 2571 c-60 -103 -68 -131 -39 -131 25 0 145 199 130 217 -20 25 -34 12 -91 -86z"></path>
    <path d="M1942 2434 c-56 -80 -102 -152 -102 -160 0 -8 8 -14 18 -14 14 1 179 212 215 276 8 14 -7 44 -22 44 -4 0 -53 -66 -109 -146z"></path>
    <path d="M2103 2380 c-40 -48 -73 -96 -73 -105 0 -35 39 -8 114 78 62 72 75 92 65 103 -6 8 -17 14 -23 13 -6 0 -44 -40 -83 -89z"></path>
    <path d="M2226 2271 c-62 -62 -87 -94 -83 -105 4 -9 11 -16 16 -16 15 0 181 160 181 175 0 7 -6 17 -13 23 -10 9 -34 -9 -101 -77z"></path>
    <path d="M2333 2150 c-61 -54 -90 -86 -86 -95 9 -25 26 -17 108 51 113 93 111 90 95 108 -7 9 -15 16 -19 16 -4 0 -48 -36 -98 -80z"></path>
    <path d="M2495 2068 c-228 -168 -254 -190 -249 -203 4 -8 11 -15 16 -15 6 0 75 45 155 101 100 70 143 106 141 117 -5 25 -29 24 -63 0z"></path>
    <path d="M1865 1944 c-161 -147 -345 -232 -409 -190 -31 20 -60 21 -76 2 -19 -24 -7 -59 30 -84 31 -21 43 -23 105 -19 39 3 81 8 95 12 16 5 -84 -102 -264 -283 -273 -274 -291 -290 -315 -281 -37 14 -64 4 -101 -37 -28 -33 -31 -42 -26 -78 l6 -41 -288 -288 -289 -289 -41 21 c-89 45 -189 36 -258 -23 -31 -27 -36 -36 -30 -59 10 -40 46 -45 93 -13 45 31 110 35 153 10 66 -40 84 -134 37 -201 -31 -45 -27 -80 11 -90 35 -9 53 5 85 68 24 45 28 63 25 114 -2 36 -12 77 -25 101 l-21 41 291 290 292 290 42 -4 c39 -5 46 -2 79 31 34 34 36 40 31 79 l-6 43 289 289 c286 286 289 289 274 240 -28 -90 -7 -184 48 -219 13 -8 22 -6 37 8 25 23 26 42 5 85 -13 27 -14 40 -5 78 25 97 120 250 210 338 42 40 53 79 29 103 -23 23 -53 11 -113 -44z" style="transform-origin: 1000px 1000px; transform: rotate(var(--myRot));"></path>
    <path d="M2590 1928 c-155 -98 -169 -108 -164 -123 4 -8 11 -15 17 -15 13 0 205 109 212 120 8 12 -6 40 -19 40 -6 0 -27 -10 -46 -22z"></path>
    <path d="M2605 1745 c-93 -50 -120 -75 -96 -89 5 -3 59 17 121 46 93 43 110 55 108 72 -2 11 -10 22 -18 23 -8 0 -60 -23 -115 -52z"></path>
    <path d="M2668 1593 c-60 -25 -108 -48 -108 -52 0 -3 4 -12 9 -20 7 -11 30 -6 125 29 102 38 117 46 114 64 -2 11 -10 22 -18 23 -8 1 -63 -19 -122 -44z"></path>
    <path d="M2666 1420 c-157 -51 -178 -62 -158 -82 9 -9 52 0 184 39 146 42 173 53 173 69 0 31 -39 26 -199 -26z"></path>
    <path d="M2763 1276 c-99 -23 -117 -33 -103 -56 7 -12 22 -11 107 4 125 24 143 31 143 56 0 26 -24 25 -147 -4z"></path>
    <path d="M2795 1111 c-89 -13 -110 -19 -113 -34 -5 -27 17 -29 139 -15 111 13 114 14 114 38 0 31 -5 31 -140 11z"></path>
    <path d="M2813 943 c-100 -4 -135 -16 -118 -43 10 -15 252 -13 258 3 6 17 -11 47 -26 45 -7 -1 -58 -4 -114 -5z"></path>
    <path d="M2574 759 c-3 -6 -2 -15 4 -21 11 -11 184 -26 305 -27 64 -1 67 0 67 23 0 22 -4 23 -82 29 -131 11 -286 8 -294 -4z"></path>
    <path d="M1680 621 l-35 -6 -3 -302 -2 -303 60 0 60 0 0 159 c0 173 -1 171 59 171 53 0 60 -21 63 -180 l3 -145 60 0 60 0 0 155 c0 174 -9 203 -76 248 -29 20 -44 23 -102 20 l-67 -3 0 98 c0 108 5 103 -80 88z"></path>
    <path d="M2143 563 l-23 -4 0 -275 0 -274 65 0 65 0 0 95 0 95 58 0 c109 0 185 37 221 107 14 28 14 128 0 156 -23 44 -64 78 -113 92 -47 14 -217 19 -273 8z m243 -118 c19 -12 24 -24 24 -60 0 -55 -21 -71 -103 -81 l-57 -7 0 80 c0 44 3 83 8 87 11 12 102 -1 128 -19z"></path>
    <path d="M2650 431 c-22 -6 -25 -12 -21 -47 1 -21 5 -42 7 -45 2 -3 37 -4 77 -1 66 4 77 2 96 -17 39 -39 28 -51 -47 -51 -125 0 -195 -61 -178 -154 19 -99 120 -135 293 -105 l63 11 0 155 c0 183 -8 211 -74 243 -44 21 -160 27 -216 11z m180 -286 l0 -45 -53 0 c-55 0 -77 12 -77 41 0 32 29 49 81 49 l49 0 0 -45z"></path>
  </g>
</svg>

Hier wäre eine HTML Seite zum rumprobieren mit dem barometer.svg, dass dann aufgepeppt ist mit dem Parameter --myRot zum Rotieren:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Bla</title>
</head>
<body>

  <button class="rotate-btn" onclick="rotateSVG(15)">Rotate SVG +</button>
  <button class="rotate-btn" onclick="rotateSVG(-15)">Rotate SVG -</button>
 
  <div id="svg-container">
    <svg class="icon weather_barometric_pressure" data-txt="weather_barometric_pressure" version="1.0" xmlns="http://www.w3.org/2000/svg" width="468pt" height="470pt" viewBox="0 0 468 470" preserveAspectRatio="xMidYMid meet" style="--myRot: 0deg;">
  <metadata> Created by potrace 1.8, written by Peter Selinger 2001-2007 </metadata>
  <g transform="translate(0,470) scale(0.158108,-0.158108)" stroke="none">
    <path d="M702 2958 c-6 -9 -6 -71 2 -188 11 -173 11 -175 35 -178 l24 -4 -8 184 c-7 171 -12 197 -36 198 -4 0 -11 -6 -17 -12z"></path>
    <path d="M887 2963 c-3 -4 -6 -62 -8 -128 -4 -117 -3 -120 18 -123 20 -3 22 2 28 70 10 110 4 182 -17 186 -9 2 -18 -1 -21 -5z"></path>
    <path d="M1057 2906 c-17 -118 -19 -203 -7 -211 21 -13 33 12 46 102 20 133 19 153 -10 153 -20 0 -24 -6 -29 -44z"></path>
    <path d="M1230 2863 c-7 -32 -18 -86 -25 -120 -13 -66 -12 -73 16 -73 13 0 21 19 37 88 38 164 38 162 9 162 -23 0 -27 -6 -37 -57z"></path>
    <path d="M1366 2710 c-46 -161 -51 -207 -24 -198 15 5 121 336 114 355 -3 7 -14 13 -24 13 -15 0 -26 -29 -66 -170z"></path>
    <path d="M1575 2808 c-2 -7 -20 -55 -40 -106 -19 -52 -35 -100 -35 -108 0 -17 36 -19 43 -1 59 141 86 211 81 218 -8 13 -44 11 -49 -3z"></path>
    <path d="M1689 2643 c-48 -103 -54 -133 -27 -133 16 0 118 189 118 219 0 15 -6 21 -20 21 -17 0 -31 -22 -71 -107z"></path>
    <path d="M1837 2571 c-60 -103 -68 -131 -39 -131 25 0 145 199 130 217 -20 25 -34 12 -91 -86z"></path>
    <path d="M1942 2434 c-56 -80 -102 -152 -102 -160 0 -8 8 -14 18 -14 14 1 179 212 215 276 8 14 -7 44 -22 44 -4 0 -53 -66 -109 -146z"></path>
    <path d="M2103 2380 c-40 -48 -73 -96 -73 -105 0 -35 39 -8 114 78 62 72 75 92 65 103 -6 8 -17 14 -23 13 -6 0 -44 -40 -83 -89z"></path>
    <path d="M2226 2271 c-62 -62 -87 -94 -83 -105 4 -9 11 -16 16 -16 15 0 181 160 181 175 0 7 -6 17 -13 23 -10 9 -34 -9 -101 -77z"></path>
    <path d="M2333 2150 c-61 -54 -90 -86 -86 -95 9 -25 26 -17 108 51 113 93 111 90 95 108 -7 9 -15 16 -19 16 -4 0 -48 -36 -98 -80z"></path>
    <path d="M2495 2068 c-228 -168 -254 -190 -249 -203 4 -8 11 -15 16 -15 6 0 75 45 155 101 100 70 143 106 141 117 -5 25 -29 24 -63 0z"></path>
    <path d="M1865 1944 c-161 -147 -345 -232 -409 -190 -31 20 -60 21 -76 2 -19 -24 -7 -59 30 -84 31 -21 43 -23 105 -19 39 3 81 8 95 12 16 5 -84 -102 -264 -283 -273 -274 -291 -290 -315 -281 -37 14 -64 4 -101 -37 -28 -33 -31 -42 -26 -78 l6 -41 -288 -288 -289 -289 -41 21 c-89 45 -189 36 -258 -23 -31 -27 -36 -36 -30 -59 10 -40 46 -45 93 -13 45 31 110 35 153 10 66 -40 84 -134 37 -201 -31 -45 -27 -80 11 -90 35 -9 53 5 85 68 24 45 28 63 25 114 -2 36 -12 77 -25 101 l-21 41 291 290 292 290 42 -4 c39 -5 46 -2 79 31 34 34 36 40 31 79 l-6 43 289 289 c286 286 289 289 274 240 -28 -90 -7 -184 48 -219 13 -8 22 -6 37 8 25 23 26 42 5 85 -13 27 -14 40 -5 78 25 97 120 250 210 338 42 40 53 79 29 103 -23 23 -53 11 -113 -44z" style="transform-origin: 1000px 1000px; transform: rotate(var(--myRot)); transition: transform 1s ease-in-out;"></path>
    <path d="M2590 1928 c-155 -98 -169 -108 -164 -123 4 -8 11 -15 17 -15 13 0 205 109 212 120 8 12 -6 40 -19 40 -6 0 -27 -10 -46 -22z"></path>
    <path d="M2605 1745 c-93 -50 -120 -75 -96 -89 5 -3 59 17 121 46 93 43 110 55 108 72 -2 11 -10 22 -18 23 -8 0 -60 -23 -115 -52z"></path>
    <path d="M2668 1593 c-60 -25 -108 -48 -108 -52 0 -3 4 -12 9 -20 7 -11 30 -6 125 29 102 38 117 46 114 64 -2 11 -10 22 -18 23 -8 1 -63 -19 -122 -44z"></path>
    <path d="M2666 1420 c-157 -51 -178 -62 -158 -82 9 -9 52 0 184 39 146 42 173 53 173 69 0 31 -39 26 -199 -26z"></path>
    <path d="M2763 1276 c-99 -23 -117 -33 -103 -56 7 -12 22 -11 107 4 125 24 143 31 143 56 0 26 -24 25 -147 -4z"></path>
    <path d="M2795 1111 c-89 -13 -110 -19 -113 -34 -5 -27 17 -29 139 -15 111 13 114 14 114 38 0 31 -5 31 -140 11z"></path>
    <path d="M2813 943 c-100 -4 -135 -16 -118 -43 10 -15 252 -13 258 3 6 17 -11 47 -26 45 -7 -1 -58 -4 -114 -5z"></path>
    <path d="M2574 759 c-3 -6 -2 -15 4 -21 11 -11 184 -26 305 -27 64 -1 67 0 67 23 0 22 -4 23 -82 29 -131 11 -286 8 -294 -4z"></path>
    <path d="M1680 621 l-35 -6 -3 -302 -2 -303 60 0 60 0 0 159 c0 173 -1 171 59 171 53 0 60 -21 63 -180 l3 -145 60 0 60 0 0 155 c0 174 -9 203 -76 248 -29 20 -44 23 -102 20 l-67 -3 0 98 c0 108 5 103 -80 88z"></path>
    <path d="M2143 563 l-23 -4 0 -275 0 -274 65 0 65 0 0 95 0 95 58 0 c109 0 185 37 221 107 14 28 14 128 0 156 -23 44 -64 78 -113 92 -47 14 -217 19 -273 8z m243 -118 c19 -12 24 -24 24 -60 0 -55 -21 -71 -103 -81 l-57 -7 0 80 c0 44 3 83 8 87 11 12 102 -1 128 -19z"></path>
    <path d="M2650 431 c-22 -6 -25 -12 -21 -47 1 -21 5 -42 7 -45 2 -3 37 -4 77 -1 66 4 77 2 96 -17 39 -39 28 -51 -47 -51 -125 0 -195 -61 -178 -154 19 -99 120 -135 293 -105 l63 11 0 155 c0 183 -8 211 -74 243 -44 21 -160 27 -216 11z m180 -286 l0 -45 -53 0 c-55 0 -77 12 -77 41 0 32 29 49 81 49 l49 0 0 -45z"></path>
  </g>
</svg>
  </div>

  <script>
    function rotateSVG(bla) {
      const svgElement = document.querySelector('.icon');
      let currentRotation = parseInt(getComputedStyle(svgElement).getPropertyValue('--myRot') || 0);
      const newRotation = currentRotation + bla;
      svgElement.style.setProperty('--myRot', `${newRotation}deg`);
    }
  </script>

</body>
</html>

Für mehr habe ich gerade keine Zeit...

Habe das mal über die Windrose gelegt... Ich denke es ist das, was dir vorschwebte.
Du darfst diesen Dateianhang nicht ansehen.
Du darfst diesen Dateianhang nicht ansehen.

Damian

Ich benutze das Icon wind.svg und drehe es über transform-rotate:

my $svg_icon=::FW_makeImage($ic);
    if(!($svg_icon =~ s/\sheight="[^"]*"/ height="18"/)) {
        $svg_icon =~ s/svg/svg height="18"/ }
    if(!($svg_icon =~ s/\swidth="[^"]*"/ width="18"/)) {
        $svg_icon =~ s/svg/svg width="18"/ }
    $out.='<g transform="translate('.$ix.', '.$iy.') translate(9, 9) scale('.$iscale.') translate(-9, -9) rotate('.$rotate.',9,9) ">';
    $out.= $svg_icon;
    $out.='</g>';
Programmierte FHEM-Module: DOIF-FHEM, DOIF-Perl, DOIF-uiTable, THRESHOLD, FHEM-Befehl: IF

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;
}

Taipan72