▁ ▂ ▃ ▄ ▅ ▆ ▇ █ 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 {\
  return FW_makeImage('light_question') if (ReadingsVal($name, "availability", "offline") eq "offline");;\
  \
  my $val = 100 + ReadingsNum($name, 'Wifi_Signal', 0);;\
  my $bargraph = FW_makeImage('bargraph');;\
  $bargraph =~ s/(<svg[^>]+style="[^"]*)--rssi:\d{1,3}/$1--rssi:$val/;;\
  \
  return '<div style="display: flex;; align-items: baseline;;"><span>'. ReadingsVal($name, "state", "???") ."</span>&nbsp;;".$bargraph."</div>";;\
}