Angeregt von Damians Flows in seiner Energiefluss-Grafik (hier (https://wiki.fhem.de/wiki/DOIF/uiTable_Anwendung#Visualisierung:_Energiefluss_als_Energie-Karte)) habe ich eine grafische Darstellung und Bedienung unserer kontrollierten Wohnraumlüftung gebastelt (mit KI-Unterstützung).
Die skalierbare `uiTable`-Karte zeigt die Temperaturen von Frischluft, Zuluft, Abluft und Fortluft im Raumluftgerät, die Stellwerte beider Lüfter sowie CO₂-Konzentration, Außentemperatur im Wechsel mit Luftfeuchtigkeit und die mittlere Ablufttemperatur des Vortags. Animierte, sich kreuzende Luftwege veranschaulichen die Strömungsrichtungen. Steht der zugehörige Steller für die Lüftergeschwindigkeit auf `0 %`, wird der Luftweg ausgegraut und die Animation abgeschaltet.
Ein Klick öffnet eine vergrößerte Popup-Ansicht. Nur dort erscheint am unteren Rand die Betriebsartenwahl `auto`, `max`, `min` und `AUS`, die den gewählten Wert in den Dummy `Lueftung` schreibt. Dieser wird von einem weiteren DOIF als Regler ausgelesen, der die Hersteller-Regelung ersetzt und eine CO2-Messung im Abluftstrom als Regelungsgröße realisiert.
defmod DI_KWL DOIF {}
attr DI_KWL event-on-change-reading .*
attr DI_KWL group KWL
attr DI_KWL room Aktuell,Debug
attr DI_KWL sortby 2
attr DI_KWL stateFormat [DI_Lueftung:state] [DI_EWT:state]
attr DI_KWL uiTable {\
$ATTRIBUTESFIRST = 1;;\
\
$TABLE = 'zoom: 0.8;; width: 360px;; height: 240px;; text-align: left;; vertical-align: top;; border-radius:0%;; position:relative;; background: linear-gradient(to bottom, rgb(40,40,40), rgb(60,60,60));; ';;\
\
package ui_Table;;\
\
## Transparente Klickflaeche ueber der gesamten Karte\
## Popup-Ziel an einer Stelle festlegen\
## Popup doppelt so gross wie die Dashboard-Karte anzeigen\
\
sub vent_popup_link {\
\
my $ziel = "DI_KWL";;\
\
my $link = ::DOIF_tablePopUp(\
$ziel,\
$ziel,\
"WRG"\
);;\
\
return '<style>'\
. '.ventPopupHit > a {'\
. 'position:absolute;;'\
. 'left:0;;top:0;;'\
. 'width:100%;;height:100%;;'\
. 'display:block;;'\
. 'box-sizing:border-box;;'\
. 'opacity:0;;'\
. 'cursor:pointer;;'\
. '}'\
\
## Nur die Ziel-Tabelle innerhalb eines Popup-Dialogs vergroessern\
## Dashboard-Zoom 0.8 wird im Popup durch 1.6 ersetzt\
. '.ui-dialog table[uitabid="DOIF-' . $ziel . '"] {'\
. 'zoom:1.6 !important;;'\
. '}'\
\
. '</style>'\
\
. '<div class="ventPopupHit" '\
. 'title="Vergrößerte Ansicht öffnen" '\
. 'style="'\
. 'position:absolute;;'\
. 'left:0;;top:0;;'\
. 'width:360px;;height:240px;;'\
. 'z-index:20;;">'\
. $link\
. '</div>';;\
}\
\
## Numerische Werte lediglich fuer die Anzeige formatieren\
## Keine Numerikpruefung und keine Werteextraktion\
\
sub vent_number_text {\
my ($value, $decimal) = @_;;\
\
return sprintf("%." . $decimal . "f", $value);;\
}\
\
\
## Stufenloser Temperaturfarbverlauf\
## RGB-Interpolation zwischen den Stuetzpunkten\
## Unterhalb und oberhalb der Skala bleibt die Endfarbe erhalten\
\
sub vent_temp_color {\
my ($temp) = @_;;\
\
my @farben = (\
[-5, 85, 35, 190],\
[ 0, 20, 45, 180],\
[ 8, 0, 115, 235],\
[15, 90, 205, 255],\
[18, 45, 195, 105],\
[24, 115, 205, 45],\
[27, 255, 165, 25],\
[31, 245, 65, 20],\
[35, 150, 0, 15]\
);;\
\
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 $unten = $farben[$i];;\
my $oben = $farben[$i + 1];;\
\
next unless $temp >= $unten->[0]\
&& $temp <= $oben->[0];;\
\
my $anteil = ($temp - $unten->[0])\
/ ($oben->[0] - $unten->[0]);;\
\
my @rgb;;\
\
for my $kanal (1..3) {\
push @rgb, int(\
$unten->[$kanal]\
+ $anteil * ($oben->[$kanal] - $unten->[$kanal])\
+ 0.5\
);;\
}\
\
return sprintf("#%02x%02x%02x", @rgb);;\
}\
}\
\
\
## Gemeinsamer Rahmen fuer alle rechteckigen Widgets\
## Einheitliche Breite 52 und Hoehe 35\
\
sub vent_box {\
my ($content) = @_;;\
\
return '<div style="'\
. 'box-sizing:border-box;;'\
. 'width:52px;;height:35px;;'\
. 'border:1px solid rgba(255,255,255,0.25);;'\
. 'border-radius:6px;;'\
. 'background:#303030;;'\
. 'display:flex;;'\
. 'align-items:center;;'\
. 'justify-content:center;;'\
. 'font-family:Arial Narrow,Roboto Condensed,sans-serif;;'\
. 'white-space:nowrap;;'\
. 'color:silver;;">'\
. $content\
. '</div>';;\
}\
\
\
## Positionierung mit Gegenzoom\
## Alle rechteckigen Widgets verwenden denselben Faktor\
\
sub vent_box_position {\
my ($left, $top, $origin, $content) = @_;;\
\
return '<div style="'\
. 'position:absolute;;'\
. 'left:' . $left . 'px;;'\
. 'top:' . $top . 'px;;'\
. 'width:52px;;height:35px;;'\
. 'transform:scale(1.5);;'\
. 'transform-origin:' . $origin . ';;'\
. 'z-index:4;;">'\
. vent_box($content)\
. '</div>';;\
}\
\
\
## Temperaturinhalt mit stufenlosem Farbverlauf\
\
sub vent_temp_content {\
my ($label, $temp) = @_;;\
\
my $anzeige = vent_number_text($temp, 1);;\
my $farbe = vent_temp_color($temp);;\
\
return '<div style="'\
. 'display:flex;;'\
. 'flex-direction:column;;'\
. 'align-items:center;;'\
. 'justify-content:center;;">'\
. '<div style="font-size:10.4px;;line-height:12px;;">'\
. $label\
. '</div>'\
. '<div style="font-size:15.6px;;line-height:18px;;'\
. 'font-weight:bold;;color:' . $farbe . ';;">'\
. $anzeige . ' ;°;C'\
. '</div></div>';;\
}\
\
\
## Fortluft links oben\
\
sub fortluft_widget {\
my ($temp) = @_;;\
\
return vent_box_position(\
2, 2, "left top",\
vent_temp_content("Fortluft", $temp)\
);;\
}\
\
\
## Frischluft links unten\
\
sub frischluft_widget {\
my ($temp) = @_;;\
\
return vent_box_position(\
2, 203, "left bottom",\
vent_temp_content("Frischluft", $temp)\
);;\
}\
\
\
## Zuluft rechts oben\
\
sub zuluft_widget {\
my ($temp) = @_;;\
\
return vent_box_position(\
306, 2, "right top",\
vent_temp_content("Zuluft", $temp)\
);;\
}\
\
\
## Abluft rechts unten\
\
sub abluft_widget {\
my ($temp) = @_;;\
\
return vent_box_position(\
306, 203, "right bottom",\
vent_temp_content("Abluft", $temp)\
);;\
}\
\
\
## Aussentemperatur und Feuchte abwechselnd anzeigen\
## Vorhandene CSS-Klassen humchange und tempchange verwenden\
## Gleicher Rahmen und Gegenzoom wie bei den anderen Widgets\
\
sub vent_outside_widget {\
my ($temp, $humidity) = @_;;\
\
my $t = vent_number_text($temp, 1);;\
my $h = vent_number_text($humidity, 0);;\
my $farbe = vent_temp_color($temp);;\
\
my $content = '<div style="'\
. 'position:relative;;'\
. 'width:100%;;height:100%;;'\
. 'text-align:center;;">';;\
\
## Wechselanzeige Luftfeuchte\
$content .= '<div class="humchange" style="'\
. 'position:absolute;;left:0;;top:0;;'\
. 'width:100%;;height:100%;;">'\
. '<div style="'\
. 'font-size:10.4px;;line-height:12px;;margin-top:2px;;">'\
. 'Feuchte'\
. '</div>'\
. '<div style="'\
. 'font-size:15.6px;;line-height:18px;;'\
. 'font-weight:bold;;color:red;;">'\
. $h . ' ;%'\
. '</div></div>';;\
\
## Wechselanzeige Aussentemperatur\
$content .= '<div class="tempchange" style="'\
. 'position:absolute;;left:0;;top:0;;'\
. 'width:100%;;height:100%;;">'\
. '<div style="'\
. 'font-size:10.4px;;line-height:12px;;margin-top:2px;;">'\
. 'AT'\
. '</div>'\
. '<div style="'\
. 'font-size:15.6px;;line-height:18px;;'\
. 'font-weight:bold;;color:' . $farbe . ';;">'\
. $t . ' ;°;C'\
. '</div></div>';;\
\
$content .= '</div>';;\
\
return vent_box_position(\
2, 127, "left top", $content\
);;\
}\
\
## Temperaturmittelwert vom Vortag\
## Rechts oberhalb von Abluft\
\
sub vent_yesterday_widget {\
my ($temp) = @_;;\
\
return vent_box_position(\
306, 127, "right top",\
vent_temp_content("Ø; gestern", $temp)\
);;\
}\
\
\
## Kreuzende Luftstroeme\
## Abluft rechts unten nach Fortluft links oben\
## Frischluft links unten nach Zuluft rechts oben\
## Bei Stellerwert 0: jeweiliger Luftweg grau und ohne Animation\
\
sub kreuzwaerme {\
my ($abluft_flow, $frischluft_flow) = @_;;\
\
## Eindeutige SVG-IDs, damit Dashboard und Popup nicht kollidieren\
our $vent_svg_seq;;\
$vent_svg_seq = 0 unless defined $vent_svg_seq;;\
$vent_svg_seq++;;\
\
my $uid = 'ventX_' . $vent_svg_seq;;\
\
my $out = '<svg xmlns="http://www.w3.org/2000/svg" '\
. 'viewBox="0 0 360 240" width="360" height="240" '\
. 'style="position:absolute;;left:0;;top:0;;'\
. 'width:360px;;height:240px;;'\
. 'display:block;;pointer-events:none;;z-index:1;;">';;\
\
## Abluft: unten rot nach Fortluft oben blau\
## Frischluft: unten blau nach Zuluft oben rot\
my @stroeme = (\
[\
"ab",\
$abluft_flow,\
"M 280 211.75 C 210 211.75 150 28.25 80 28.25",\
"#ff453a",\
"#2196f3"\
],\
[\
"frisch",\
$frischluft_flow,\
"M 80 211.75 C 150 211.75 210 28.25 280 28.25",\
"#2196f3",\
"#ff453a"\
]\
);;\
\
## Gemeinsame Zeitbasis fuer beide Laufbalken\
## Ein kompletter Wechsel dauert acht Sekunden\
## Jeder Durchlauf dauert 3.6 Sekunden\
## Zwischen den Durchlaeufen liegen 0.4 Sekunden Pause\
my $zyklus = 8;;\
my $grau = '#808080';;\
\
for my $strom (@stroeme) {\
my ($id, $flow, $pfad, $startfarbe, $zielfarbe) = @$strom;;\
\
## Numerisch interpretieren;; leere oder ungueltige Werte gelten als 0\
$flow = 0 unless defined $flow && $flow =~ /^-?\d+(?:\.\d+)?$/;;\
$flow += 0;;\
\
my $aktiv = $flow > 0;;\
\
my $gradient_id = $uid . '_farbe_' . $id;;\
my $pfeil_id = $uid . '_pfeil_' . $id;;\
\
my $linienfarbe = $aktiv\
? 'url(#' . $gradient_id . ')'\
: $grau;;\
\
my $pfeilfarbe = $aktiv ? $zielfarbe : $grau;;\
\
$out .= '<defs>';;\
\
## Farbverlauf von unten nach oben\
$out .= '<linearGradient id="' . $gradient_id . '" '\
. 'gradientUnits="userSpaceOnUse" '\
. 'x1="0" y1="211.75" x2="0" y2="28.25">'\
. '<stop offset="0%" stop-color="' . $startfarbe . '"/>'\
. '<stop offset="43%" stop-color="' . $startfarbe . '"/>'\
. '<stop offset="57%" stop-color="' . $zielfarbe . '"/>'\
. '<stop offset="100%" stop-color="' . $zielfarbe . '"/>'\
. '</linearGradient>';;\
\
## Pfeilspitze passend zum Betriebszustand\
$out .= '<marker id="' . $pfeil_id . '" '\
. 'viewBox="0 0 12 12" refX="12" refY="6" '\
. 'markerUnits="userSpaceOnUse" '\
. 'markerWidth="10" markerHeight="10" orient="auto">'\
. '<path d="M 0 0 L 12 6 L 0 12 Z" '\
. 'fill="' . $pfeilfarbe . '"/>'\
. '</marker>';;\
\
$out .= '</defs>';;\
\
## Breiter Grundbalken\
$out .= '<path d="' . $pfad . '" '\
. 'fill="none" stroke="' . $linienfarbe . '" '\
. 'stroke-width="8" stroke-opacity="0.35" '\
. 'stroke-linecap="round"/>';;\
\
## Duennere Linie mit Pfeilspitze\
$out .= '<path d="' . $pfad . '" '\
. 'fill="none" stroke="' . $linienfarbe . '" '\
. 'stroke-width="2" stroke-opacity="0.85" '\
. 'marker-end="url(#' . $pfeil_id . ')"/>';;\
\
## Bei Stellerwert 0 keinen Laufbalken und keine Animation erzeugen\
next unless $aktiv;;\
\
## Ein einzelner heller Laufbalken\
## Grosser Abstand verhindert weitere Balken im Pfad\
$out .= '<path d="' . $pfad . '" pathLength="100" '\
. 'fill="none" stroke="' . $linienfarbe . '" '\
. 'stroke-width="8" stroke-linecap="round" '\
. 'stroke-dasharray="18 200" '\
. 'stroke-dashoffset="18">';;\
\
if ($id eq "ab") {\
## Abluft in der ersten Zyklushaelfte\
$out .= '<animate '\
. 'attributeName="stroke-dashoffset" '\
. 'values="18;;-100;;-100" '\
. 'keyTimes="0;;0.45;;1" '\
. 'dur="' . $zyklus . 's" begin="0s" '\
. 'repeatCount="indefinite" calcMode="linear"/>';;\
\
## Nach dem Durchlauf unsichtbar\
$out .= '<animate '\
. 'attributeName="stroke-opacity" '\
. 'values="1;;0;;0" '\
. 'keyTimes="0;;0.45;;1" '\
. 'dur="' . $zyklus . 's" begin="0s" '\
. 'repeatCount="indefinite" calcMode="discrete"/>';;\
} else {\
## Frischluft in der zweiten Zyklushaelfte\
$out .= '<animate '\
. 'attributeName="stroke-dashoffset" '\
. 'values="18;;18;;-100;;-100" '\
. 'keyTimes="0;;0.5;;0.95;;1" '\
. 'dur="' . $zyklus . 's" begin="0s" '\
. 'repeatCount="indefinite" calcMode="linear"/>';;\
\
## Nur waehrend des eigenen Durchlaufs sichtbar\
$out .= '<animate '\
. 'attributeName="stroke-opacity" '\
. 'values="0;;1;;0;;0" '\
. 'keyTimes="0;;0.5;;0.95;;1" '\
. 'dur="' . $zyklus . 's" begin="0s" '\
. 'repeatCount="indefinite" calcMode="discrete"/>';;\
}\
\
$out .= '</path>';;\
}\
\
$out .= '</svg>';;\
\
## Reine HTML-Rueckgabe;; uiTable aktualisiert das gesamte Widget bei Events\
return $out;;\
}\
\
\
## Zentralwidget mit kompakter Zahlengruppe\
## Abluft und erster Wert um einen Pixel nach oben\
## Zuluft und zweiter Wert um einen Pixel nach unten\
## Mittelpunkt auf der Flow-Kreuzung bei 180 / 120\
\
sub vent_fan_circle {\
my ($ab, $zu) = @_;;\
\
my $ring = ring2(\
$ab,\
0,\
100,\
undef,\
undef,\
"Abluft",\
120,\
[(33.3,40,66.6,65,100,85)],\
"0,,fill:silver, %",\
$zu,\
0,\
100,\
190,\
170,\
"Zuluft",\
undef,\
"0,,fill:silver, %",\
undef,\
undef,\
"nogradient,,innerring,nopointer"\
);;\
\
my $html = ref($ring) eq "ARRAY"\
? $ring->[0]\
: $ring;;\
\
my $ab_text = sprintf("%.0f", $ab);;\
my $zu_text = sprintf("%.0f", $zu);;\
\
## Ringgrafik auf feste Abmessungen setzen\
## Originaltexte werden separat neu angeordnet\
my $out = '<style>'\
. '.ventCentralRing svg {'\
. 'width:110px !important;;'\
. 'height:110px !important;;'\
. 'display:block;;'\
. '}'\
. '.ventCentralRing svg text {'\
. 'display:none !important;;'\
. '}'\
. '</style>';;\
\
$out .= '<div style="'\
. 'position:absolute;;'\
. 'left:180px;;top:120px;;'\
. 'transform:translate(-50%,-50%);;'\
. 'width:110px;;height:110px;;'\
. 'border-radius:50%;;'\
. 'background:#303030;;'\
. 'z-index:5;;">';;\
\
## Ringsegmente\
$out .= '<div class="ventCentralRing" style="'\
. 'position:absolute;;left:0;;top:0;;'\
. 'width:110px;;height:110px;;">'\
. $html\
. '</div>';;\
\
## Gemeinsame Textebene\
$out .= '<div style="'\
. 'position:absolute;;inset:0;;'\
. 'font-family:Arial Narrow,Roboto Condensed,sans-serif;;'\
. 'text-align:center;;'\
. 'white-space:nowrap;;'\
. 'color:silver;;'\
. 'pointer-events:none;;">';;\
\
## Abluft-Beschriftung\
$out .= '<div style="'\
. 'position:absolute;;left:0;;right:0;;top:18px;;'\
. 'font-size:15.6px;;line-height:18px;;">'\
. 'Abluft'\
. '</div>';;\
\
## Abluft-Prozentwert\
$out .= '<div style="'\
. 'position:absolute;;left:0;;right:0;;top:35px;;'\
. 'font-size:23.4px;;line-height:23.4px;;'\
. 'font-weight:bold;;">'\
. $ab_text\
. '<span style="font-size:15.6px;;font-weight:normal;;">'\
. ' ;%</span>'\
. '</div>';;\
\
## Zuluft-Prozentwert\
$out .= '<div style="'\
. 'position:absolute;;left:0;;right:0;;top:55px;;'\
. 'font-size:23.4px;;line-height:23.4px;;'\
. 'font-weight:bold;;">'\
. $zu_text\
. '<span style="font-size:15.6px;;font-weight:normal;;">'\
. ' ;%</span>'\
. '</div>';;\
\
## Zuluft-Beschriftung\
$out .= '<div style="'\
. 'position:absolute;;left:0;;right:0;;top:77px;;'\
. 'font-size:15.6px;;line-height:18px;;">'\
. 'Zuluft'\
. '</div>';;\
\
$out .= '</div></div>';;\
\
return ref($ring) eq "ARRAY"\
? [$out, $ring->[1]]\
: $out;;\
}\
\
\
## CO2-Widget \
## Farbring als Ampel\
\
## CO2-Anzeige auf Basis des urspruenglichen icon_uring\
## Kein Icon\
## CO2 als Text oben\
## Messwert und Einheit ppm durch das Widget selbst\
\
sub vent_co2_widget {\
my ($co2) = @_;;\
\
my $widget = icon_uring(\
"0,0,1,8",\
"",\
$co2,\
600,\
1300,\
undef,\
undef,\
"ppm",\
"0,fill:silver,fill:silver",\
130,\
[(800,120,1000,60,1300,0)]\
);;\
\
my $html = ref($widget) eq "ARRAY"\
? $widget->[0]\
: $widget;;\
\
## Nur die aeussere Grafik skalieren\
## Keine SVG-Texte ausblenden\
my $out = '<style>'\
. '.ventCO2Top > svg {'\
. 'width:60px !important;;'\
. 'height:60px !important;;'\
. 'display:block;;'\
. '}'\
. '</style>';;\
\
$out .= '<div class="ventCO2Top" style="'\
. 'position:absolute;;'\
. 'left:180px;;top:2px;;'\
. 'transform:translateX(-50%);;'\
. 'width:60px;;height:60px;;'\
. 'z-index:6;;">'\
. $html;;\
\
## Text anstelle des bisherigen Icons\
$out .= '<div style="'\
. 'position:absolute;;'\
. 'left:-1px;;right:1px;;top:13px;;'\
. 'text-align:center;;'\
. 'font-family:Arial Narrow,Roboto Condensed,sans-serif;;'\
. 'font-size:10.4px;;line-height:12px;;'\
. 'color:silver;;'\
. 'pointer-events:none;;">'\
. 'CO2'\
. '</div>';;\
\
$out .= '</div>';;\
\
return ref($widget) eq "ARRAY"\
? [$out, $widget->[1]]\
: $out;;\
}\
\
\
## Bedienleiste nur im Popup anzeigen\
## Mittig unten zwischen den beiden Temperaturwidgets\
## Oberhalb der transparenten Popup-Klickflaeche positionieren\
\
sub vent_popup_control_style {\
\
return '<style>'\
\
## In der normalen Dashboard-Karte unsichtbar\
. 'table[uitabid="DOIF-DI_KWL"] .ventPopupControl {'\
. 'display:none;;'\
. '}'\
\
## Nur innerhalb eines Popup-Dialogs einblenden\
. '.ui-dialog table[uitabid="DOIF-DI_KWL"] .ventPopupControl {'\
. 'display:flex;;'\
. 'position:absolute;;'\
. 'left:82px;;'\
. 'bottom:4px;;'\
. 'width:196px;;'\
. 'min-height:28px;;'\
. 'box-sizing:border-box;;'\
. 'align-items:center;;'\
. 'justify-content:center;;'\
. 'padding:3px 2px;;'\
. 'border:1px solid rgba(255,255,255,0.25);;'\
. 'border-radius:6px;;'\
. 'background:#303030;;'\
. 'color:silver;;'\
. 'font-family:Arial Narrow,Roboto Condensed,sans-serif;;'\
. 'font-size:12px;;'\
. 'white-space:nowrap;;'\
. 'z-index:30;;'\
. 'pointer-events:auto;;'\
. '}'\
\
## Im bereits geoeffneten Popup keine weitere Popup-Klickflaeche\
. '.ui-dialog table[uitabid="DOIF-DI_KWL"] .ventPopupHit {'\
. 'display:none;;'\
. '}'\
\
. '</style>';;\
}\
\
}## Ende des Perlbereichs\
\
## Tabellendefinition\
## Jeden Aufruf in einer Zeile belassen\
## Flows direkt an die Prozentwerte der Steller koppeln\
## Bedienleiste nur im Popup sichtbar\
\
fortluft_widget([Fortluft:temperature:d1]).\
frischluft_widget([Frischluft:temperature:d1]).\
zuluft_widget([T.Zuluft2:temperature:d1]).\
abluft_widget([Abluft:temperature:d1]).\
kreuzwaerme([Steller_Abluft:Stufe],[Steller_Zuluft:Stufe]).\
vent_fan_circle([Steller_Abluft:Stufe],[Steller_Zuluft:Stufe]).\
vent_outside_widget([TF.Aussen:temperature:d1],[TF.Aussen:humidity:d0]).\
vent_yesterday_widget([Abluft:temperature_AVG_LD:d1]).\
vent_co2_widget([CO2Sensor:CO2:d]).\
vent_popup_control_style().\
"<div class='ventPopupControl'>".\
widget([Lueftung:state],"uzsuSelectRadio,auto,max,min,AUS","set").\
"</div>".\
vent_popup_link()
attr DI_KWL verbose 0
setstate DI_KWL auto EWT wärmt
setstate DI_KWL 2026-10-14 14:03:59 abluft_flow
setstate DI_KWL 2026-10-15 11:36:02 abluft_temp 19.4
setstate DI_KWL 2026-10-15 11:28:27 fortluft_temp 13.7
setstate DI_KWL 2026-10-14 14:03:59 frischluft_flow
setstate DI_KWL 2026-10-15 12:24:28 frischluft_temp 13.5
setstate DI_KWL 2026-10-18 00:27:08 mode enabled
setstate DI_KWL 2026-10-18 00:27:09 state initialized
setstate DI_KWL 2026-10-15 12:16:30 zuluft_temp
Freue mich auf Verbesserungsvorschläge und wenn's jemand anderen nützt oder auf eine Idee bringt.
Und ehe jemand Fake ruft: Vor dem Eintritt in das Raumluftgerät wird die Luft über einen Erdwärmetauscher im Winter angewärmt (daher der Unterschied zwischen AT und Frischluft) und im Sommer (bei umgangenen Wärmetaucher) gekühlt.
Christian