FHEM Forum

FHEM => Automatisierung => DOIF => Thema gestartet von: cwagner am 17 Oktober 2026, 12:08:33

Titel: Visualisierung eines Schichtenspeichers in der Heizungsalage
Beitrag von: cwagner am 17 Oktober 2026, 12:08:33
Angeregt von Damians genialer Energiefluss-Grafik (hier entlang (https://wiki.fhem.de/wiki/DOIF/uiTable_Anwendung#Visualisierung:_Energiefluss_als_Energie-Karte)) habe ich davon ausgehend eine Visualisierung der Wärmeflüsse unserer Heizungsanlage als DOIF uiTable gebastelt. Dabei habe ich mich von KI bei der Recherche unterstützen lassen.

Unsere Anlage arbeitet mit einem zentralen Schichtenspeicher, durch den die Trinkwasserleitung im Wendel hindurchgeführt wird und so im Zapfvorgang durch die gespeicherte Energie im Speicher erwärmt wird (Hygienespeicher-Prinzip). Der Speicher hat fünf Temperaturebenen, die von einer Wärmepumpe und einer Thermo-Solaranlage beladen werden und entsprechend auch 5 Zapfebenen (5=Warmwasser, 1 ist die Kühlebene, für den Fall, dass die Wärmepumpe im Umkehrbetrieb als Kühlpumpe arbeitet im Sommer). Die Visualisierung "sortiert" die Entnahmen nach links, die Beladung nach rechts. In der Abbildung hat die Wärmepumpe Pause, weil die Thermosolaranlage den aktuellen Wärmeverbrauch trägt.

Gerne stelle ich meinen Stand als Steinbruch für andere und zur Diskussion bereit und bin natürlich auch für Verbesserungsvorschläge dankbar. Ich frische aktuell Jahrzehnte ruhende Programmiererfahrung wieder auf: meine Lösung ist bei weitem nicht so elegant und lesbar wie die zitierte Energycard.

Für die Darstellung in einem Dashboard habe ich die Grafik über die Zoomfunktion verkleinert und über eine Popup-Funktion unter Nutzung der entsprechenden uitable Funktion kann man per Klick in die Kachel eine vergrößerte Darstellung als Overlay aufrufen.

defmod di_Link3 DOIF {}
attr di_Link3 group KWL
attr di_Link3 room Aktuell,Debug
attr di_Link3 uiTable {\
$ATTRIBUTESFIRST = 1;;\
$SHOWNODEVICELINE = "Aktuell";;\
\
$TABLE = 'zoom:0.8;; width:360px;; height:435px;; position:relative;; text-align:left;; vertical-align:top;; border-radius:0%;; background:linear-gradient(to bottom,rgb(40,40,40),rgb(60,60,60));; ';;\
\
package ui_Table;;\
\
\
## RGB-Farbskala mit stufenloser Interpolation\
\
sub l3_rgb_scale {\
  my ($temp, $farben) = @_;;\
\
  if ($temp <= $farben->[0]->[0]) {\
    return sprintf(\
      "#%02x%02x%02x",\
      @{$farben->[0]}[1,2,3]\
    );;\
  }\
\
  if ($temp >= $farben->[-1]->[0]) {\
    return sprintf(\
      "#%02x%02x%02x",\
      @{$farben->[-1]}[1,2,3]\
    );;\
  }\
\
  for (my $i = 0;; $i < $#$farben;; $i++) {\
    my $a = $farben->[$i];;\
    my $b = $farben->[$i + 1];;\
\
    next unless $temp >= $a->[0] && $temp <= $b->[0];;\
\
    my $f = ($temp - $a->[0]) / ($b->[0] - $a->[0]);;\
\
    my @rgb;;\
\
    for my $k (1..3) {\
      push @rgb, int(\
        $a->[$k] + $f * ($b->[$k] - $a->[$k]) + 0.5\
      );;\
    }\
\
    return sprintf("#%02x%02x%02x", @rgb);;\
  }\
\
  return "#888888";;\
}\
\
\
## Gemeinsame Temperaturfarbskala\
##\
##  15 Grad: Blau\
##  35 Grad: Tuerkis\
##  45 Grad: Gruen\
##  55 Grad: Gelb\
##  65 Grad: Orange\
##  72 Grad: Rot\
##  80 Grad: Tiefrot\
## 100 Grad: Rotviolett\
## 120 Grad: Violett\
\
sub l3_color {\
  my ($temp) = @_;;\
\
  return l3_rgb_scale(\
    $temp,\
    [\
      [ 15,  30,  70, 230],\
      [ 25,  40, 150, 245],\
      [ 35,  35, 195, 180],\
      [ 45, 115, 200,  65],\
      [ 55, 245, 210,  35],\
      [ 65, 255, 135,  25],\
      [ 72, 240,  55,  25],\
      [ 80, 165,  10,  25],\
      [100, 175,  20, 125],\
      [120, 145,  45, 220]\
    ]\
  );;\
}\
\
\
## Separate Farbskala fuer den Warmwasserabgang\
## Ab 42 Grad Rottoene\
## Bei 60 Grad kraeftiges Rot\
\
sub l3_ww_color {\
  my ($temp) = @_;;\
\
  return l3_rgb_scale(\
    $temp,\
    [\
      [15,  30,  55, 170],\
      [30,  43, 175, 210],\
      [38, 245, 175,  65],\
      [42, 245, 105,  80],\
      [50, 250,  65,  50],\
      [60, 255,  20,  20],\
      [75, 220,   0,   0]\
    ]\
  );;\
}\
\
\
## Flussdarstellung\
##\
## Pfadrichtung = Flussrichtung\
## Inaktiv bleibt nur die dezente Grundlinie sichtbar.\
## Aktiv erscheinen Pfeilspitze und Laufbalken.\
\
sub l3_flow {\
  my ($pfad, $farbe, $aktiv, $start) = @_;;\
\
  my $out = '<path d="' . $pfad . '" '\
    . 'fill="none" stroke="' . $farbe . '" '\
    . 'stroke-opacity="0.25" stroke-width="5" '\
    . 'stroke-linecap="round"/>';;\
\
  if ($aktiv) {\
    $out .= '<path d="' . $pfad . '" '\
      . 'fill="none" stroke="' . $farbe . '" '\
      . 'stroke-width="2" stroke-opacity="0.85" '\
      . 'marker-end="url(#l3_arrow)"/>';;\
\
    $out .= '<path d="' . $pfad . '" pathLength="100" '\
      . 'fill="none" stroke="' . $farbe . '" '\
      . 'stroke-width="5" stroke-linecap="round" '\
      . 'stroke-dasharray="16 84">';;\
\
    $out .= '<animate attributeName="stroke-dashoffset" '\
      . 'from="100" to="0" dur="4s" '\
      . 'begin="' . $start . 's" '\
      . 'repeatCount="indefinite" calcMode="linear"/>';;\
\
    $out .= '</path>';;\
  }\
\
  return $out;;\
}\
\
\
## Ringwidget positionieren\
##\
## Aeussere SVG-Anzeigeflaeche: 60 x 60 px\
## Positionierung anhand des Mittelpunkts\
## Titel nahe am unteren offenen Kreisabschnitt\
##\
## Nur das erste, aeussere SVG wird angepasst.\
\
sub l3_ring_slot {\
  my ($cx, $cy, $widget, $titel) = @_;;\
\
  $widget = $widget->[0] if ref($widget) eq "ARRAY";;\
\
  $widget =~ s{\
    <svg\b([^>]*)>\
  }{\
    my $attr = $1;;\
\
    $attr =~ s/\swidth\s*=\s*"[^"]*"//gi;;\
    $attr =~ s/\sheight\s*=\s*"[^"]*"//gi;;\
    $attr =~ s/\sstyle\s*=\s*"[^"]*"//gi;;\
\
    $attr =~ s/\swidth\s*=\s*'[^']*'//gi;;\
    $attr =~ s/\sheight\s*=\s*'[^']*'//gi;;\
    $attr =~ s/\sstyle\s*=\s*'[^']*'//gi;;\
\
    '<svg' . $attr\
      . ' width="60" height="60"'\
      . ' style="display:block;;width:60px;;height:60px;;'\
      . 'position:static;;margin:0;;padding:0;;">';;\
  }eix;;\
\
  my $out = '<div style="position:absolute;;'\
    . 'left:' . ($cx - 30) . 'px;;'\
    . 'top:' . ($cy - 30) . 'px;;'\
    . 'width:60px;;height:60px;;line-height:0;;">'\
    . $widget\
    . '</div>';;\
\
  if (defined($titel) && $titel ne "") {\
    $out .= '<div style="position:absolute;;'\
      . 'left:' . ($cx - 51) . 'px;;'\
      . 'top:' . ($cy + 22) . 'px;;'\
      . 'width:102px;;height:16px;;'\
      . 'text-align:center;;white-space:nowrap;;'\
      . 'font-family:Arial Narrow,Roboto Condensed,sans-serif;;'\
      . 'font-size:12px;;line-height:16px;;color:silver;;">'\
      . $titel\
      . '</div>';;\
  }\
\
  return $out;;\
}\
\
## COP-/Leistungswidget\
##\
## Nullanzeigen und getrennte Dezimalreste entfernen:\
## 0 / 0.0 / 0.00 / .0 / .00 / ,0\
##\
## Beschriftungen und Ringdarstellung bleiben erhalten.\
\
sub l3_cop_widget {\
  my ($cop, $leistung) = @_;;\
\
  my $widget = ring2(\
    $cop, 1, 8, 0, 120, "COP", 130, undef, 1,\
    $leistung, 1, 10, 120, 0, "kWhT", undef, 1\
  );;\
\
  my $html = ref($widget) eq "ARRAY"\
    ? $widget->[0]\
    : $widget;;\
\
  ## Nur bearbeiten, wenn beide angezeigten Werte\
  ## numerisch Null sind. Bei gemischten Werten\
  ## werden Dezimalreste nicht pauschal entfernt.\
\
  if ($cop == 0 && $leistung == 0) {\
\
    $html =~ s{(?<=>)(?:\s|&nbsp;;|&#160;;|&#xA0;;)*[+-]?(?:0(?:[.,]0+)?|[.,]0+)(?:\s|&nbsp;;|&#160;;|&#xA0;;)*(?=<)}{}gi;;\
\
  } else {\
\
    ## Zusammenhaengende Nullanzeigen entfernen.\
    ## Getrennte Dezimalteile bei gemischten Werten\
    ## benoetigen eine gezielte Zuordnung zum Messwert.\
\
    $html =~ s{(?<=>)(?:\s|&nbsp;;|&#160;;|&#xA0;;)*[+-]?0(?:[.,]0+)?(?:\s|&nbsp;;|&#160;;|&#xA0;;)*(?=<)}{}gi;;\
\
  }\
\
  if (ref($widget) eq "ARRAY") {\
    $widget->[0] = $html;;\
    return $widget;;\
  }\
\
  return $html;;\
}\
## Solarwidget mit bedingter Kollektortemperatur\
##\
## Inaktiv:\
## Ring und normale Beschriftung Solar\
##\
## Aktiv:\
## Ring, Kollektortemperatur und darunter Solar\
\
sub l3_solar_slot {\
  my ($widget, $aktiv, $temp) = @_;;\
\
  my $cx = 305;;\
  my $cy = 343;;\
\
  if (!$aktiv) {\
    return l3_ring_slot(\
      $cx, $cy, $widget, "Solar"\
    );;\
  }\
\
  my $farbe = l3_color($temp);;\
\
  my $out = l3_ring_slot(\
    $cx, $cy, $widget, ""\
  );;\
\
  ## Kollektortemperatur zwischen Ring und Solar-Titel\
\
  $out .= '<div style="position:absolute;;'\
    . 'left:' . ($cx - 51) . 'px;;'\
    . 'top:' . ($cy + 22) . 'px;;'\
    . 'width:102px;;height:17px;;'\
    . 'text-align:center;;white-space:nowrap;;'\
    . 'font-family:Arial Narrow,Roboto Condensed,sans-serif;;'\
    . 'font-size:13px;;font-weight:bold;;line-height:17px;;'\
    . 'color:' . $farbe . ';;">'\
    . sprintf("%.0f", $temp)\
    . ' &#176;;C'\
    . '</div>';;\
\
  ## Solar-Titel bei aktivem Flow weiter unten\
\
  $out .= '<div style="position:absolute;;'\
    . 'left:' . ($cx - 51) . 'px;;'\
    . 'top:' . ($cy + 40) . 'px;;'\
    . 'width:102px;;height:16px;;'\
    . 'text-align:center;;white-space:nowrap;;'\
    . 'font-family:Arial Narrow,Roboto Condensed,sans-serif;;'\
    . 'font-size:12px;;line-height:16px;;color:silver;;">'\
    . 'Solar'\
    . '</div>';;\
\
  return $out;;\
}\
\
\
## Popup mit Hilfe der uiTable-Funktion\
##\
##\
## Dashboard-Zoom: 0.8\
## Popup-Zoom:     1.6\
##\
## 12.5 interne Pixel ergeben bei Zoom 0.8\
## einen freien Klickrand von 10 dargestellten Pixeln.\
\
sub l3_popup_link {\
\
  my $ziel = "di_Link3";;\
\
  my $link = ::DOIF_tablePopUp(\
    $ziel,\
    $ziel,\
    "remotecontrol/black_btn_ONSCREEN"\
  );;\
\
  return '<style>'\
\
    . '.l3PopupHit > a {'\
    . 'position:absolute;;'\
    . 'left:0;;top:0;;'\
    . 'width:100%;;height:100%;;'\
    . 'display:block;;'\
    . 'box-sizing:border-box;;'\
    . 'opacity:0;;'\
    . 'cursor:pointer;;'\
    . '}'\
\
    ## Speicherkarte im Popup vergroessern\
\
    . '.ui-dialog table[uitabid="DOIF-' . $ziel . '"] {'\
    . 'zoom:1.6 !important;;'\
    . '}'\
\
    ## Keine erneute Klickflaeche im Popup\
\
    . '.ui-dialog table[uitabid="DOIF-' . $ziel . '"] '\
    . '.l3PopupHit {'\
    . 'display:none !important;;'\
    . '}'\
\
    ## Zentrierung des Speicher-Dialogs\
    ## Erfordert CSS-:has-Unterstuetzung\
\
    . '.ui-dialog:has(table[uitabid="DOIF-' . $ziel . '"]) {'\
    . 'position:fixed !important;;'\
    . 'left:50% !important;;'\
    . 'top:50% !important;;'\
    . 'transform:translate(-50%,-50%);;'\
    . '}'\
\
    . '</style>'\
\
    . '<div class="l3PopupHit" '\
    . 'title="Vergrößerte Ansicht öffnen" '\
    . 'style="'\
    . 'position:absolute;;'\
    . 'left:12.5px;;top:12.5px;;'\
    . 'width:335px;;height:410px;;'\
    . 'z-index:30;;">'\
    . $link\
    . '</div>';;\
}\
\
\
## Gesamte Speicherkarte\
##\
## Parameter:\
## E1 bis E5\
## HK1 / HK2 / WP / Solar / WW-Zapfung\
## Widgets HK1 / HK2 / WP-COP / WP-Temperatur / Solar\
## Solar-Vorlauf / Solar-Ruecklauf\
## WP-Vorlauf / WP-Ruecklauf\
## Kollektortemperatur\
## HT-Heizen / Brauchwasser-Heizen\
##\
\
sub l3_card {\
  my (\
    $e1, $e2, $e3, $e4, $e5,\
    $hk1, $hk2, $wp, $solar, $zapfung,\
    $hk1_widget, $hk2_widget,\
    $wp_cop_widget, $wp_temp_widget, $solar_widget,\
    $solar_vl, $solar_rl,\
    $wp_vl, $wp_rl,\
    $kollektor_temp,\
    $ht_heizen, $bw_heizen\
  ) = @_;;\
\
  ## Alternative Betriebsarten aktivieren ebenfalls\
  ## den jeweiligen gemeinsamen Ruecklauf.\
\
  my $hk1_flow_aktiv = ($hk1 || $ht_heizen) ? 1 : 0;;\
  my $wp_flow_aktiv  = ($wp  || $bw_heizen) ? 1 : 0;;\
\
  my @temp = ($e1, $e2, $e3, $e4, $e5);;\
\
    my $out = '<div style="position:absolute;;left:0;;top:0;;'\
    . 'width:360px;;height:435px;;overflow:hidden;;">';;\
\
  ## Gemeinsam verschobene Darstellung\
$out .= '<div style="position:absolute;;left:0;;top:-22px;;'\
    . 'width:360px;;height:480px;;">';;\
\
  $out .= '<svg xmlns="http://www.w3.org/2000/svg" '\
    . 'viewBox="0 0 360 480" width="360" height="480" '\
    . 'style="position:absolute;;left:0;;top:0;;'\
    . 'display:block;;width:360px;;height:480px;;'\
    . 'font-family:Arial Narrow,Roboto Condensed,sans-serif;;">';;\
\
\
  ## SVG-Definitionen\
\
  $out .= '<defs>';;\
\
  $out .= '<clipPath id="l3_tank_clip">'\
    . '<rect x="122" y="55" width="116" height="360" rx="32"/>'\
    . '</clipPath>';;\
\
  $out .= '<marker id="l3_arrow" '\
    . 'viewBox="0 0 10 10" refX="10" refY="5" '\
    . 'markerUnits="userSpaceOnUse" '\
    . 'markerWidth="7" markerHeight="7" orient="auto">'\
    . '<path d="M 0 0 L 10 5 L 0 10 Z" fill="silver"/>'\
    . '</marker>';;\
\
  $out .= '</defs>';;\
\
\
  ## Warmwasserabgang\
  ## Eigene Farbskala, Temperatur aus E1\
\
  my $ww_farbe = l3_ww_color($e1);;\
\
  $out .= l3_flow(\
    "M 180 55 L 180 43.5 Q 180 33.5 190 33.5 L 252 33.5",\
    $ww_farbe,\
    $zapfung,\
    0\
  );;\
\
  $out .= '<text x="260" y="38.5" '\
    . 'fill="' . $ww_farbe . '" font-size="14">'\
    . 'Warmwasser</text>';;\
\
\
  ## Trinkwasser kalt\
  ## Konstante Farbe entsprechend 30 Grad\
  ## Beschriftung bei x=21\
\
  my $kw_farbe = l3_color(30);;\
\
  $out .= l3_flow(\
    "M 108 441 L 168 441 Q 180 441 180 429 L 180 415",\
    $kw_farbe,\
    $zapfung,\
    1\
  );;\
\
  $out .= '<text x="21" y="446" '\
    . 'fill="' . $kw_farbe . '" font-size="13">'\
    . 'Trinkwasser kalt</text>';;\
\
\
  ## HK1\
  ## Fester Widget-Mittelpunkt x=55 / y=199\
  ## Grenze zwischen E2 und E3\
  ##\
  ## Normal: Vorlauf aus E2\
  ## HT:     Vorlauf aus E1\
  ##\
  ## Nur der ausgewaehlte Vorlaufpfad ist sichtbar.\
  ## HT-Heizen hat Vorrang.\
\
  if ($ht_heizen) {\
\
    $out .= l3_flow(\
      "M 122 91 L 112 91 Q 102 91 102 101 L 102 174 Q 102 184 92 184 L 81 184",\
      l3_color($e1),\
      1,\
      0.6\
    );;\
\
  } else {\
\
    $out .= l3_flow(\
      "M 122 163 L 108 163 Q 98 163 98 173 L 98 174 Q 98 184 88 184 L 81 184",\
      l3_color($e2),\
      $hk1,\
      0.6\
    );;\
\
  }\
\
\
  ## HK1-Ruecklauf\
  ## Unterer Widgetanschluss -> E3\
  ## In beiden Betriebsarten derselbe Pfad\
\
  $out .= l3_flow(\
    "M 81 214 L 88 214 Q 98 214 98 224 L 98 225 Q 98 235 108 235 L 122 235",\
    l3_color($e3),\
    $hk1_flow_aktiv,\
    1.6\
  );;\
\
\
  ## HK2 FBH\
  ## Vorlauf aus E4\
  ## Ruecklauf nach E5\
\
  $out .= l3_flow(\
    "M 122 319 L 108 319 Q 98 319 98 323.5 Q 98 328 89 328 L 81 328",\
    l3_color($e4),\
    $hk2,\
    1.2\
  );;\
\
  $out .= l3_flow(\
    "M 81 358 L 90 358 Q 100 358 100 368 L 100 369 Q 100 379 110 379 L 122 379",\
    l3_color($e5),\
    $hk2,\
    2.2\
  );;\
\
\
  ## Lambda\
  ##\
  ## Normal:\
  ## Oberer WP-Widgetanschluss -> E3\
  ##\
  ## Warmwasser-Anforderung:\
  ## Oberer WP-Widgetanschluss -> E1\
  ##\
  ## Warmwasser-Anforderung hat Vorrang.\
  ## Der nicht ausgewaehlte Vorlauf bleibt unsichtbar.\
  ##\
  ## Farben aus den echten WP-Temperaturen.\
\
  if ($bw_heizen) {\
\
    $out .= l3_flow(\
      "M 279 256 L 262 256 Q 248 256 248 242 L 248 121 Q 248 111 238 111",\
      l3_color($wp_vl),\
      1,\
      0.8\
    );;\
\
  } else {\
\
    $out .= l3_flow(\
      "M 279 256 L 272 256 Q 262 256 262 246 L 262 245 Q 262 235 252 235 L 238 235",\
      l3_color($wp_vl),\
      $wp,\
      0.8\
    );;\
\
  }\
\
\
  ## Gemeinsamer Lambda-Ruecklauf\
  ## E4 -> unterer WP-Widgetanschluss\
\
  $out .= l3_flow(\
    "M 238 295 L 252 295 Q 262 295 262 290.5 Q 262 286 271 286 L 279 286",\
    l3_color($wp_rl),\
    $wp_flow_aktiv,\
    1.8\
  );;\
\
\
  ## Solar\
  ## Warmer Vorlauf: oberer Widgetanschluss -> E4\
  ## Kuehler Ruecklauf: E5 -> unterer Widgetanschluss\
  ##\
  ## Freigabe:\
  ## SW_Heizung1:RL_Sperre eq ON\
\
  $out .= l3_flow(\
    "M 279 328 L 271 328 Q 262 328 262 323.5 Q 262 319 252 319 L 238 319",\
    l3_color($solar_vl),\
    $solar,\
    2.4\
  );;\
\
  $out .= l3_flow(\
    "M 238 379 L 252 379 Q 262 379 262 369 L 262 368 Q 262 358 272 358 L 279 358",\
    l3_color($solar_rl),\
    $solar,\
    3.4\
  );;\
\
\
  ## Speichergrundkoerper\
\
  $out .= '<rect x="122" y="55" '\
    . 'width="116" height="360" rx="32" '\
    . 'fill="#252525"/>';;\
\
\
  ## Fuenf Temperaturschichten\
  ## E1 oben bis E5 unten\
  \
  $out .= '<g clip-path="url(#l3_tank_clip)">';;\
\
  for (my $i = 0;; $i < 5;; $i++) {\
    my $y = 55 + $i * 72;;\
    my $farbe = l3_color($temp[$i]);;\
\
    $out .= '<rect x="122" y="' . $y . '" '\
      . 'width="116" height="72" '\
      . 'fill="' . $farbe . '" fill-opacity="0.18"/>';;\
\
    $out .= '<rect x="126" y="' . ($y + 7) . '" '\
      . 'width="4" height="58" rx="2" '\
      . 'fill="' . $farbe . '" fill-opacity="0.85"/>';;\
\
    if ($i > 0) {\
      $out .= '<line x1="130" y1="' . $y . '" '\
        . 'x2="230" y2="' . $y . '" '\
        . 'stroke="#888888" stroke-opacity="0.3" '\
        . 'stroke-width="1"/>';;\
    }\
\
    $out .= '<text x="180" y="' . ($y + 45) . '" '\
      . 'text-anchor="middle" fill="' . $farbe . '" '\
      . 'font-size="27" font-weight="bold">'\
      . sprintf("%.1f", $temp[$i])\
      . '<tspan font-size="16"> &#176;;C</tspan>'\
      . '</text>';;\
  }\
\
  $out .= '</g>';;\
\
\
  ## Speicherumrandung\
\
  $out .= '<rect x="122" y="55" '\
    . 'width="116" height="360" rx="32" '\
    . 'fill="none" stroke="#909090" stroke-width="2"/>';;\
\
  $out .= '</svg>';;\
\
\
  ## HK1\
  ## Feste Position an der Grenze E2/E3\
  ## Beschriftung auch im HT-Betrieb unveraendert\
\
  $out .= l3_ring_slot(\
    55, 199, $hk1_widget, "HK1 RAD"\
  );;\
\
\
  ## HK2 an der Grenze E4/E5\
\
  $out .= l3_ring_slot(\
    55, 343, $hk2_widget, "HK2 FBH"\
  );;\
\
\
  ## COP und Leistung ohne Symbol\
  ## Nullwerte werden durch l3_cop_widget ausgeblendet.\
\
  $out .= l3_ring_slot(\
    305, 199, $wp_cop_widget, ""\
  );;\
\
\
  ## Lambda-Temperaturwidget mit angeschlossenen Flows\
\
  $out .= l3_ring_slot(\
    305, 271, $wp_temp_widget, "Wärmepumpe"\
  );;\
\
\
  ## Solar mit bedingter Kollektortemperatur\
\
  $out .= l3_solar_slot(\
    $solar_widget,\
    $solar,\
    $kollektor_temp\
  );;\
\
\
  ## Ende der gemeinsam verschobenen Darstellung\
\
  $out .= '</div>';;\
\
\
  ## Popup-Klickflaeche ausserhalb der Verschiebung\
\
  $out .= l3_popup_link();;\
\
  $out .= '</div>';;\
\
\
  ## Absicherung fuer die Uebertragung des HTML-Inhalts\
  ## in einem einfach zitierten JavaScript-String.\
  ##\
  ## Nach dem Anhaengen aller Widgets\
  ## und des Popup-Links ausfuehren.\
\
  $out =~ s/'/&#39;;/g;;\
\
\
  ## Echte Zeilenumbrueche aus Widgets und Popup-Link entfernen.\
\
  $out =~ s/[\r\n]+/ /g;;\
\
  return $out;;\
}\
\
}\
\
## Tabellendefinition\
##\
## Speichertemperaturen E1 bis E5\
##\
## Normale Flow-Freigaben:\
## HK1 / HK2 / WP: Betriebsmodus beginnt mit Heizen\
## Solar: SW_Heizung1:RL_Sperre ist ON\
##\
## Alternative Vorlaeufe:\
## HT: Betriebsmodus_HT ist exakt Heizen\
## WW: Betriebsmodus_Brauchwasser ist exakt Heizen\
##\
## Trinkwasserfluss fest aktiv.\
##\
## HK1 normal: E2 -> E3\
## HK1 HT:     E1 -> E3\
## HK2:        E4 -> E5\
## Lambda:     E3 -> E4\
## Lambda WW:  E1 -> E4\
##\
## COP-/Leistungswidget ueber l3_cop_widget:\
## Reine Nullwerte werden ausgeblendet.\
\
l3_card([T_SP_Ebene_1:temperature:d1],[T_SP_Ebene_2:temperature:d1],[T_SP_Ebene_3:temperature:d1],[T_SP_Ebene_4:temperature:d1],[T_SP_Ebene_5:temperature:d1],([Lambda:Betriebsmodus_HK1] =~ '^Heizen' ? 1 : 0),([Lambda:Betriebsmodus_HK2] =~ '^Heizen' ? 1 : 0),([Lambda:Betriebsmodus_WP] =~ '^Heizen' ? 1 : 0),([SW_Heizung1:RL_Sperre] eq 'ON' ? 1 : 0),1,ring2([Lambda:T_Vorlauf_HK1:d],15,[Lambda:T_Vorlauf_berechnet_nach_Heizkurve_HK1:d],240,120,"°VL",130,undef,1,[Lambda:T_Ruecklauf_HK1:d],15,([Lambda:T_Vorlauf_berechnet_nach_Heizkurve_HK1:d]-5),240,120,"°RL",undef,1),ring2([Lambda:T_Vorlauf_HK2:d],15,[Lambda:T_Vorlauf_berechnet_nach_Heizkurve_HK2:d],240,120,"°VL",130,undef,1,[Lambda:T_Ruecklauf_HK2:d],15,([Lambda:T_Vorlauf_berechnet_nach_Heizkurve_HK2:d]-5),240,120,"°RL",undef,1),l3_cop_widget([Lambda:COP:d],[Lambda:Abgabeleistung_WP:d]),ring2([Lambda:T_Vorlauf_WP:d],10,80,240,0,"°VL",130,undef,1,[Lambda:T_Ruecklauf_WP:d],10,80,240,0,"°RL",undef,1,120),ring2([T_VL_Kollektor:temperature:d],10,120,195,0,"°VL",130,undef,1,[T_RL_Kollektor:temperature:d],10,120,195,9,"°RL",undef,1,120),[T_VL_Kollektor:temperature:d],[T_RL_Kollektor:temperature:d],[Lambda:T_Vorlauf_WP:d],[Lambda:T_Ruecklauf_WP:d],[T_Kollektor:temperature:d],([Lambda:Betriebsmodus_HT] eq 'Heizen' ? 1 : 0),([Lambda:Betriebsmodus_Brauchwasser] eq 'Heizen' ? 1 : 0))

setstate di_Link3 initialized
setstate di_Link3 2026-10-15 18:20:44 mode enabled
setstate di_Link3 2026-10-15 18:20:44 state initialized


Christian