http://wiki.fhem.de/w/api.php?action=feedcontributions&user=Albatros&feedformat=atomFHEMWiki - Benutzerbeiträge [de]2024-03-28T14:37:18ZBenutzerbeiträgeMediaWiki 1.39.3http://wiki.fhem.de/w/index.php?title=HourCounter&diff=9413HourCounter2015-01-18T09:55:35Z<p>Albatros: /* Betriebsstundenzähler über Leistungsmessung ableiten */</p>
<hr />
<div>{{Infobox Modul<br />
|ModPurpose=HourCounter dient zum Zählen von Ereignissen und zur Erfassung von Betriebs-/Ruhe-Zeiten<br />
|ModType=h<br />
<!-- |ModCmdRef=LightScene -- nicht erforderlich, da Modultyp x --><br />
|ModForumArea=MAX<br />
|ModTechName=98_HourCounter.pm<br />
|ModOwner=John ([http://forum.fhem.de/index.php?action=profile;u=806 Forum] / [[Benutzer Diskussion:John|Wiki]])}}<br />
<br />
<br />
[[HourCounter]] ist ein Perl-Modul, das die Anzahl von Ereignissen erfasst. Bei bipolaren Ereignissen wird zusätzlich die Puls- sowie die Pausendauer ermittelt. Im vorliegenden Beispiel wird ein Betriebsstundenzähler mit einem MAX-Fensterkontakt realisiert.<br />
<br />
== Features ==<br />
* Ermittlung von Betriebsstunden, Auslastung, Verbräuchen, Schalthäufigkeiten<br />
* Ermittlung der Häufigkeit, Puls- Pausendauer pro Tag<br />
* Ermittlung der Puls-/Pausendauer der letzten Schaltperiode<br />
* Bereitstellung von Ereignissen zur Fortführung der Kumulation für Tages-, Wochen- und Monatswerte<br />
* unterstützende Funktionen zur Ablage von Tages-, Wochen- und Monatswerten<br />
<br />
== Zielsetzung des WIKI-Artikels ==<br />
Erläuterung der Funktionalität zur weiterführenden Diskussion im [http://forum.fhem.de/index.php/topic,12216.msg72596.html#msg72596 Forum].<br />
<br />
== Aktuelle Version ==<br />
Das Modul ist seit dem 24.10.2014 Bestandteil von FHEM.<br />
<br />
== Definition ==<br />
=== Abstrakt ===<br />
<pre>define <name> HourCounter <regexp_for_ON> [<regexp_for_Off>]</pre><br />
<br />
<regexp_for_ON> ist ein regulärer Ausdruck der das Ereignis beschreibt.<br />
<br />
Wenn auch [<regexp_for_Off>] definiert ist, so sprechen wir von einem bipolarem Ereignis, das einen <br />
EIN- sowie einen AUS-Zustand aufweist.<br />
<br />
<regexp_for_ON> beschreibt in diesem Fall die positive Flanke,[<regexp_for_Off>] die negative Flanke. Die Struktur des regexp-Ausdruckes ist analog zu jener beim Notify aufgebaut.<br />
<br />
===Konkret===<br />
<pre>define CN.Test HourCounter SHUTTER.JOHN:onoff:.1 SHUTTER.JOHN:onoff:.0</pre><br />
Hier wird der HourCounter CN.TEST definiert.<br />
Ein MAX-Fensterkontakt mit Namen SHUTTER.JOHN wird als Ereignis-Geber verwendet.<br />
Das Reading "onoff" wird als Trigger für unserem Zähler genutzt.<br />
Bei den Fensterkontakten sehen diese Ereignisse wie folgt aus:<br />
<pre><br />
2013-11-15 23:19:12 MAX SHUTTER.JOHN onoff: 1<br />
....<br />
2013-11-15 23:19:24 MAX SHUTTER.JOHN onoff: 0<br />
</pre><br />
<br />
Soll ein Dummy als Ereignis-Geber verwendet werden, lautet die Definition wie folgt:<br />
<pre> define CN.Test HourCounter myDummy:1 myDummy:0</pre><br />
<br />
Die neue Instanz weist folgende Struktur auf:<br />
<br />
<pre><br />
Internals:<br />
CFGFN <br />
DEF SHUTTER.JOHN:onoff:.1 SHUTTER.JOHN:onoff:.0<br />
NAME CN.Test<br />
NR 738<br />
NTFY_ORDER 50-CN.Test<br />
STATE 1<br />
TYPE HourCounter<br />
Readings:<br />
2014-02-04 20:59:22 clearDate 2014-02-04 20:59:22<br />
2014-02-04 20:59:57 countsOverall 1<br />
2014-02-04 20:59:57 countsPerDay 1<br />
2014-02-04 20:59:57 pauseTimeIncrement 35<br />
2014-02-04 20:59:57 pauseTimeOverall 35<br />
2014-02-04 20:59:57 pauseTimePerDay 35<br />
2014-02-04 21:00:01 pulseTimeIncrement 4<br />
2014-02-04 21:00:01 pulseTimeOverall 4<br />
2014-02-04 21:00:01 pulseTimePerDay 4<br />
2014-02-04 20:59:57 state 1<br />
2014-02-04 21:00:00 tickHour 1<br />
2014-02-04 21:00:01 value 0<br />
Helper:<br />
OFF_Regexp SHUTTER.JOHN:onoff:.0<br />
ON_Regexp SHUTTER.JOHN:onoff:.1<br />
calledByEvent <br />
changedTimestamp 2014-02-04 21:00:01<br />
forceClear <br />
forceDayChange <br />
forceHourChange <br />
forceMonthChange <br />
forceWeekChange <br />
isFirstRun <br />
sdRoundHourLast 1391544000<br />
value 0<br />
cmdQueue:<br />
</pre><br />
<br />
Damit nicht zu viele Ereignisse in den Log-Dateien landen, kann man diese sinnvoll einschränken, so daß<br />
nur Änderungen das Feuern von Events auslösen<br />
<pre><br />
attr CN.BRENNER event-on-change-reading .*<br />
</pre><br />
Wenn sich nun jedoch über Stunden und Tage nichts ändert, sieht man in den Charts keine Daten mehr.<br />
Mit dieser Anweisung wird erreicht, daß alle Readings nach Aktualisierung spätesten nach 1 Stunden einen Event feuern, auch wenn sich der Wert nicht ändert.<br />
Eine Ausnahme hiervon sollen machen die tick*-Readings, deren Events sollen immer sofort gefeuert werden, <br />
wenn sie aktualisiert werden.<br />
<pre><br />
attr CN.BRENNER event-min-interval tick.*:0,.*:3600<br />
</pre><br />
<br />
== Readings ==<br />
{| class="wikitable sortable"<br />
|-<br />
! Reading !! class="unsortable" | Beschreibung <br />
|-<br />
| clearDate || Datum, zu dem alle kumulativen Readings über set .. clear gelöscht wurden<br />
|-<br />
| countsOverall || Absolutzähler für das Auftreten des ON-Ereignisses<br />
|-<br />
| countsPerDay || Tageszähler für das Auftreten des ON-Ereignisses<br />
|-<br />
| pauseTimeIncrement || Zeitdauer der Pausen-Phase in Sekunden ; wird zyklisch aktualisiert<br />
|-<br />
| pauseTimeEdge || Zeitdauer der letzten komplettierten Pausen-Phase<br />
|-<br />
| pauseTimeOverall || Zeitdauer in Sekunden über alle aufgetretenen Pause-Phasen<br />
|-<br />
| pauseTimePerDay || Zeitdauer in Sekunden über alle aufgetretenen Pause-Phasen des akt. Tages<br />
|-<br />
| pulseTimeIncrement || Zeitdauer der Puls-Phase in Sekunden ; wird zyklisch aktualisiert<br />
|-<br />
| pulseTimeEdge || Zeitdauer der letzten komplettierten Pulse-Phase<br />
|-<br />
| pulseTimeOverall || Zeitdauer in Sekunden über alle aufgetretenen Puls-Phasen<br />
|-<br />
| pulseTimePerDay || Zeitdauer in Sekunden über alle aufgetretenen Puls-Phasen des akt. Tages<br />
|-<br />
| value || Aktueller Schaltzustand gemäss ON/OFF Ereignis,<br /><br />
mit 1=letztes Ereignis war ON-Ereignis <br /><br />
und 0=letztes Ereignis war OFF-Ereignis<br />
|-<br />
| tickUpdated || Event wird gefeuert, wenn die operativen Readings beschrieben worden sind (nicht unbedingt gändert)<br />
|-<br />
| tickChanged || Event wird nach Änderung von value gefeuert<br />
|-<br />
| tickDay || Event wird nach Tageswechsel gefeuert bevor die Tageszähler resettiert werden<br />
|-<br />
| tickHour || Event wird nach Stundenwechsel gefeuert<br />
|-<br />
| tickWeek || Event wird nach Wochenwechsel gefeuert<br />
|-<br />
| tickMonth || Event wird nach Monatswechsel gefeuert<br />
|-<br />
| tickYear || Event wird nach Jahreswechsel gefeuert<br />
|-<br />
| forceHourChange || simuliert einen Stundenwechsel<br />
|-<br />
| forceDayChange || simuliert einen Tageswechsel<br />
|-<br />
| forceWeekChange || simuliert einen Wochenwechsel<br />
|-<br />
| forceMonthChange || simuliert einen Monatswechsel<br />
|-<br />
| forceYearChange || simuliert einen Jahreswechsel<br />
|}<br />
<br />
== Web-Oberfläche ==<br />
[[Datei:13_11_15_HourCounter_Face.png|HourCounter]]<br />
<br />
== Anwendung ==<br />
Nachfolgende Darstellung zeigt das Einschaltverhalten eines Heizungskessels zusammen mit den <br />
abgeleiteten Werten.<br />
<br />
[[Datei:13_11_15_HourCounter_Chart.png]]<br />
<br />
* die Kurve "Brenner EIN" zeigt die Trigger-Signale des ON/OFF Filters, also das Ein-/Ausschalten des Brenners<br />
* die Kurve "Brenner-Starts" zeigt die über den Tag aufgelaufenen Starts, also chronologisch das Anwachsen von Reading countsPerDay<br />
* die Kurve "Betriebsstunden" zeigt die aufgelaufene Zeit aus dem Reading pulseTimePerDay umgerechnet zu Stunden<br />
* die Kurve "Dauer" zeigt die Dauer des letzten Pulses in Sekunden<br />
* die Kurve Auslastung zeigt das Verhältnis des Readings pulseTimePerDay zur seit Tagesbeginn vergangenen Zeit.<br />
<br />
=== Chart der Aktualwerte ===<br />
Wir benötigen hierzu ein File-Archiv für die aufgelaufenen Daten.<br />
<pre>define CN.Test.File FileLog ./log/CN.Test-%Y.log (CN\.Test:.*)</pre><br />
Man erhält nach den ersten Ereignissen Einträge in folgender Form:<br />
<pre><br />
2013-11-16_12:45:40 CN.Test value: 1<br />
2013-11-16_12:45:40 CN.Test 1<br />
2013-11-16_12:46:21 CN.Test pulseTimeIncrement: 41 <br />
2013-11-16_12:46:21 CN.Test pulseTimePerDay: 41<br />
2013-11-16_12:46:21 CN.Test pulseTimeOverall: 41<br />
2013-11-16_12:46:21 CN.Test value: 0<br />
2013-11-16_12:50:38 CN.Test countsPerDay: 2<br />
2013-11-16_12:50:38 CN.Test countsOverall: 2<br />
2013-11-16_12:50:38 CN.Test pauseTimeIncrement: 257<br />
2013-11-16_12:50:38 CN.Test pauseTimePerDay: 756<br />
2013-11-16_12:50:38 CN.Test pauseTimeOverall: 756<br />
2013-11-16_12:50:38 CN.Test value: 1<br />
2013-11-16_12:50:38 CN.Test 2<br />
</pre><br />
<br />
Nun kann man den Chart definieren:<br />
[[Datei:13_11_16_HourCounter_ChartBuild_01.png]]<br />
<br />
Für die Kurve "Brenner EIN" verwenden wir CN.Test.value. Damit diese als unterste Kurve dargestellt wird transformieren wir den Wert 1 auf -2 und alle anderen (also die 0) auf -21 mit folgender Funktion:<br />
<pre>$fld[3]=~"1"?-2:-19</pre><br />
Die "Brenner Starts" können wir direkt von countsPerDay ableiten.<br /><br /><br />
Für die "Betriebsstunden" verwenden wir pulseTimePerDay. Da diese in Sekunden vorliegen teilen wir den Wert durch 3600, um Stunden zu erhalten.<br />
<pre>$fld[3]/=3600</pre><br />
Als letzten versorgen wir noch die Kurve "Dauer" mit pulseTimeIncrement. Da wir diese in Minuten haben wollen ist ebenfalls eine Umformung nötig.<br />
<pre>$fld[3]/=60</pre><br />
<br />
Somit sind die Basis-Kurven angelegt.<br />
<br />
=== Erweiterungen ===<br />
Es wurden im Forum viele Wünsche formuliert, weitere Funktionalitäten für den HourCounter einzuführen.<br />
<br />
* Aggregation über bestimmte oder ganz freie Zeiträume<br />
* komplexe Berechnungen, die zum Verbrauch führen<br />
* Zuordnung von Verbräuchen zu unterschiedlichen Countern nach bestimmten Bedingungen<br />
<br />
Vor allem die Aggregation erfasster Werte in Stunden-, Tages-, Wochen- und Monatswerten ist eine sinnvolle<br />
Erweiterung bei der Verbrauchserfassung.<br />
<br />
HourCounter bietet Schnittstellen an, die es ermöglichen, das Modul selbst mit neuen Eigenschaften zu erweitern.<br />
<br />
Die Referenz-Implementierung in 99_UtilsHourCounter.pm zeigt, wie dies skript-technisch zu realisieren ist.<br />
<br />
==== Installation ====<br />
Die jeweils aktuelle Version von 99_UtilsHourCounter kann über diesen <br />
[http://sourceforge.net/p/fhem/code/HEAD/tree/trunk/fhem/contrib/99_UtilsHourCounter.pm?format=raw Link] bezogen werden.<br />
<br />
==== Readings ====<br />
99_UtilsHourCounter erweitert den HourCounter um folgende Funktionen:<br />
{| class="wikitable sortable"<br />
|-<br />
! Reading !! class="unsortable" | Beschreibung<br />
|-<br />
| appCountsPerHour || Stundenzähler, wird bei Stundenwechsel aktualisiert<br />
|-<br />
| appCountsPerHourTemp || Arbeitszähler zu appCountsPerHour<br />
|-<br />
| appCountsPerDay || Tageszähler, wird bei Tageswechsel aktualisiert (Arbeitszähler ist countsPerDay)<br />
|-<br />
| appCountsPerWeek || Wochenzähler, wird bei Wochenwechsel aktualisiert<br />
|-<br />
| appCountsPerWeekTemp || Arbeitszähler zu appCountsPerWeek<br />
|-<br />
| appCountsPerMonth || Monatszähler, wird bei Monatswechsel aktualisiert<br />
|-<br />
| appCountsPerMonthTemp || Arbeitszähler zu appCountsPerMonth<br />
|-<br />
| appCountsPerYear || Jahreszähler, wird bei Jahreswechsel aktualisiert<br />
|-<br />
| appCountsPerYearTemp || Arbeitszähler zu appCountsPerYear<br />
|-<br />
| appOpHoursPerDay || Betriebsstunden des Tages<br />
|-<br />
| appOpHoursPerDayTemp || Arbeitszähler zu appOpHoursPerDay<br />
|-<br />
| appOpHoursPerWeek || Betriebsstunden der Woche<br />
|-<br />
| appOpHoursPerWeekTemp || Arbeitszähler zu appOpHoursPerWeek<br />
|-<br />
| appOpHoursPerMonth || Betriebsstunden des Monats<br />
|-<br />
| appOpHoursPerMonthTemp || Arbeitszähler appOpHoursPerMonth<br />
|-<br />
| appOpHoursPerYear || Betriebsstunden des Jahres<br />
|-<br />
| appOpHoursPerYearTemp || Arbeitszähler appOpHoursPerYear<br />
|-<br />
| appUtilization || Auslastung = pulseTimePerDay /(vergangene Sekunden seit Tagesbeginn) * 100<br />
|-<br />
| appUtilizationTemp || Arbeitsvariable zu appUtilization<br />
|}<br />
Beginn der Woche ist jeweils der Sonntag.<br /><br />
<br />
Mit folgender Anweisung aktivieren wir die Erweiterungen: <br />
:<code><br />
define CN.EVENT notify CN\..*:tick.* { appHCNotify("%NAME","%EVTPART0","%EVTPART1");;}<br />
</code><br />
<br />
Spätestens nach einer steigenden und einer fallenden Flanke sind die zuvor genannten app*-Readings zu sehen.<br />
{{Randnotiz|RNTyp=Info|RNText=Die gezeigten define-Anweisungen müssen jeweils in einer Zeile stehen (keine Zeilenumbrüche!).}}<br />
<br />
Die neuen Readings werden automatisch in den "Setter" der Web-Oberflächen aufgenommen. Dies gilt für alle Readings, die mit "app" beginnen.<br />
<br />
Somit können die neuen Readings beliebig manipuliert werden.<br />
<br />
==== Archiv für Tages-/Wochen-/Monats-/Jahreswerte anlegen ====<br />
Nun wollen wir die aggregierten Werte in eine eigene Datei speichern. Dies gelingt mit <br />
:<code>define CN.Test.FileDay FileLog ./log/CN.Test-Day-%Y.log CN.Test:app\w+ (Utilization|PerHour|PerDay|PerWeek|PerMonth|PerYear)(?!Temp).* </code><br />
<br />
<br />
Im Klartext:<br />
* verwende alle Werte des Counters CN.Test, deren Reading mit "app" beginnt<br />
* und die einen der Terme appUtilization|PerHour|PerDay|PerWeek|PerMonth|PerYear beinhalten<br />
* und die danach nicht dem Term "Temp" beinhalten<br />
<br />
== Fragen und Antworten ==<br />
==== Betriebsstundenzähler über Leistungsmessung ableiten ====<br />
'''Frage:'''<br />
Ich würde gerne zählen, wenn ich mehr Strom als Standy verbrauche (also mehr als 2Watt)<br />
und keine Betriebsstunden zählen, wenn der Verbrauch unter 2 Watt ist. Ist das möglich?<br />
<br />
<u>Beispiel für die Events</u><br />
<pre><br />
013-11-18_19:40:32 XXX power: 1.9<br />
2013-11-18_19:40:32 XXX consumption: 2<br />
2013-11-18_19:40:32 XXX consumptionTotal: 2<br />
2013-11-18_19:40:36 XXX power: 27<br />
2013-11-18_19:40:36 XXX consumption: 2<br />
2013-11-18_19:40:36 XXX consumptionTotal: 2<br />
2013-11-18_19:40:42 XXX power: 34.6<br />
2013-11-18_19:40:42 XXX consumption: 2 <br />
</pre><br />
<br />
'''Antwort'''<br />
Die hier vorgestellte Lösung überprüft ob der Wert des Events power eine oder zwei Ziffern vor dem Komma hat.<br />
Deshalb wir hier erst gezählt, wenn die Schwelle von 10Watt überschritten wird. <br />
<pre><br />
define CN.Test HourCounter XXX:power:\s[0-9]{2,}(\.[0-9]{1,3})*$ XXX:power:\s[0-9]{1}(\.[0-9]{1,3})*$<br />
</pre><br />
Erläuterung zu <regexp_for_ON> = XXX:power:\s[0-9]{2,}(\.[0-9]{1,3})*$<br />
* "XXX" bezeichnet das Device, der Term danach ist der regexp-Filte für das On-Ereignis<br />
* "power:" das Ereignis muss mit diesem Term beginnen<br />
* "\s" es muss ein Leerzeichen folgen<br />
* "[0-9]{2,}" es müssen mindestens 2 Ziffern folgen<br />
* "(\.[0-9]{1,3})*" wenn ein Punkt folgt, dann müssen auf diesen mindestens 1..3 Ziffern folgen<br />
* "$" danach darf kein weiteres Zeichen mehr folgen<br />
<br />
'''Antwort Möglichkeit 2'''<br />
In dieser Lösung bekommt das entsprechende Device was mit HourCounter überwacht werden soll ein userReadings "onoff". Dieses Reading wird dann zum Schalten von Hour Counter verwendet:<br />
<pre><br />
define GPIO4_DS18B20_Waermepumpe_Vorlauf GPIO4 28-000005956079<br />
attr GPIO4_DS18B20_Waermepumpe_Vorlauf alias Wärmepumpe Vorlauf<br />
attr GPIO4_DS18B20_Waermepumpe_Vorlauf model DS18B20<br />
attr GPIO4_DS18B20_Waermepumpe_Vorlauf room GPIO4<br />
attr GPIO4_DS18B20_Waermepumpe_Vorlauf userReadings onoff {(ReadingsVal("GPIO4_DS18B20_Waermepumpe_Vorlauf","temperature",0) >28)?1:0;;}<br />
<br />
define Waermepumpe_HourCounter HourCounter GPIO4_DS18B20_Waermepumpe_Vorlauf:onoff:.1 GPIO4_DS18B20_Waermepumpe_Vorlauf:onoff:.0<br />
attr Waermepumpe_HourCounter room 2_Fussbodenheizung<br />
</pre><br />
Erläuterungen zu dem Code: "{(ReadingsVal("GPIO4_DS18B20_Waermepumpe_Vorlauf","temperature",0) >28)?1:0;;}"<br />
* "(ReadingsVal("GPIO4_DS18B20_Waermepumpe_Vorlauf","temperature",0) >28)" Diese Bedingung für das userReadings onoff prüft bei jedem Event, ob der Wert von temperature größer als 28 ist. <br />
* "?1:0" Ist dies der Fall wird das userReading onoff auf 1 gesetzt andernfalls auf 0.<br />
Auf Basis deses UserReadings wird dann der HourCounter definiert:<br />
* "GPIO4_DS18B20_Waermepumpe_Vorlauf:onoff:.1" Einschaltbedingung für HourCounter<br />
* "GPIO4_DS18B20_Waermepumpe_Vorlauf:onoff:.0" Abschaltbedingung für Hour Counter<br />
<br />
==== Welche Anwendungsfälle sind denkbar ? ====<br />
[http://forum.fhem.de/index.php/topic,12216.msg175163.html#msg175163 Aus dem Forum]<br />
* Betriebsstundenzähler für meine "Fliegenkiller-Steckdose"<br />
* Nutzungsdauer beschränken für TV,Internet oder Spielkonsolen für entnervte Eltern<br />
* Nutzungsdauer ermitteln zur Energieeinsparung (Klimageräte, Ventilatoren, Dunstabzugshauben etc.)<br />
* Lüftungsverhalten ermitteln (wie lange Fenster pro Tag geöffnet)<br />
<br />
[http://forum.fhem.de/index.php/topic,12216.msg195358.html#msg195358 Brenner Starts/Verbrauch + akkumulierte Werte]<br />
<br />
[http://forum.fhem.de/index.php/topic,12216.msg196358.html#msg196358 Ölverbrauch+Solar-Ladung]<br />
<br />
[http://voizchat.de/gaszaehler-verbrauch-erfassen-mit-fhem-und-raspberry-gpio/ Gaszähler mit HourCounter realisieren]<br />
==== Seltene Schaltvorgänge ====<br />
Die Schaltvorgänge sind über den Tag sehr wenige. Die Aktualisierung erfolgt immer erst<br />
bei der negativen Flanke. Wie kann man eine häufigere Aktualisierung erreichen ?<br />
<br />
'''Antwort'''<br />
<br />
Ab Version 1.0.0.6 ist wurde das Attribut "interval" eingeführt; es ist auf 60 Minuten voreingestellt und kann von 5..60 im 5 Minuten-Raster festgelegt werden.<br />
Es bestimmt, nach welcher Zeit Puls-/Pausendauer aktualisiert werden sollen, unabhängig vom Auftreten einer Schaltflanke.<br />
<br />
==== Korrekte Darstellung der akkumulierten Daten im Chart ====<br />
'''Frage'''<br />
<br />
"appCountsPerDay: 4" bezieht sich auf die Counts des Tages 2014-06-16, trägt aber selbst den Zeitstempel 2014-06-17 und wird demnach in einem Chart auch über den Tag " 2014-06-17" dargestellt.<br />
Das Problem betrifft alle akkumulierten Daten des HourCounters.<br />
Wie erreicht man im Chart die korrekte Darstellung ?<br />
<br />
<br />
'''Antwort'''<br />
<br />
Das Thema wurde [http://forum.fhem.de/index.php/topic,12216.msg239929.html#msg239929 hier] disktuiert.<br />
Eine Lösung findet man mit [http://www.fhemwiki.de/wiki/LogProxy LogProxy].<br />
Damit läßt sich ein negativer Offset für die X-Achse definieren, so daß die Daten wieder korrekt dargestellt werden.<br />
<br />
[[Kategorie:MAX]]<br />
[[Kategorie:HOWTOS]]</div>Albatroshttp://wiki.fhem.de/w/index.php?title=HourCounter&diff=9412HourCounter2015-01-18T09:55:14Z<p>Albatros: /* Betriebsstundenzähler über Leistungsmessung ableiten */</p>
<hr />
<div>{{Infobox Modul<br />
|ModPurpose=HourCounter dient zum Zählen von Ereignissen und zur Erfassung von Betriebs-/Ruhe-Zeiten<br />
|ModType=h<br />
<!-- |ModCmdRef=LightScene -- nicht erforderlich, da Modultyp x --><br />
|ModForumArea=MAX<br />
|ModTechName=98_HourCounter.pm<br />
|ModOwner=John ([http://forum.fhem.de/index.php?action=profile;u=806 Forum] / [[Benutzer Diskussion:John|Wiki]])}}<br />
<br />
<br />
[[HourCounter]] ist ein Perl-Modul, das die Anzahl von Ereignissen erfasst. Bei bipolaren Ereignissen wird zusätzlich die Puls- sowie die Pausendauer ermittelt. Im vorliegenden Beispiel wird ein Betriebsstundenzähler mit einem MAX-Fensterkontakt realisiert.<br />
<br />
== Features ==<br />
* Ermittlung von Betriebsstunden, Auslastung, Verbräuchen, Schalthäufigkeiten<br />
* Ermittlung der Häufigkeit, Puls- Pausendauer pro Tag<br />
* Ermittlung der Puls-/Pausendauer der letzten Schaltperiode<br />
* Bereitstellung von Ereignissen zur Fortführung der Kumulation für Tages-, Wochen- und Monatswerte<br />
* unterstützende Funktionen zur Ablage von Tages-, Wochen- und Monatswerten<br />
<br />
== Zielsetzung des WIKI-Artikels ==<br />
Erläuterung der Funktionalität zur weiterführenden Diskussion im [http://forum.fhem.de/index.php/topic,12216.msg72596.html#msg72596 Forum].<br />
<br />
== Aktuelle Version ==<br />
Das Modul ist seit dem 24.10.2014 Bestandteil von FHEM.<br />
<br />
== Definition ==<br />
=== Abstrakt ===<br />
<pre>define <name> HourCounter <regexp_for_ON> [<regexp_for_Off>]</pre><br />
<br />
<regexp_for_ON> ist ein regulärer Ausdruck der das Ereignis beschreibt.<br />
<br />
Wenn auch [<regexp_for_Off>] definiert ist, so sprechen wir von einem bipolarem Ereignis, das einen <br />
EIN- sowie einen AUS-Zustand aufweist.<br />
<br />
<regexp_for_ON> beschreibt in diesem Fall die positive Flanke,[<regexp_for_Off>] die negative Flanke. Die Struktur des regexp-Ausdruckes ist analog zu jener beim Notify aufgebaut.<br />
<br />
===Konkret===<br />
<pre>define CN.Test HourCounter SHUTTER.JOHN:onoff:.1 SHUTTER.JOHN:onoff:.0</pre><br />
Hier wird der HourCounter CN.TEST definiert.<br />
Ein MAX-Fensterkontakt mit Namen SHUTTER.JOHN wird als Ereignis-Geber verwendet.<br />
Das Reading "onoff" wird als Trigger für unserem Zähler genutzt.<br />
Bei den Fensterkontakten sehen diese Ereignisse wie folgt aus:<br />
<pre><br />
2013-11-15 23:19:12 MAX SHUTTER.JOHN onoff: 1<br />
....<br />
2013-11-15 23:19:24 MAX SHUTTER.JOHN onoff: 0<br />
</pre><br />
<br />
Soll ein Dummy als Ereignis-Geber verwendet werden, lautet die Definition wie folgt:<br />
<pre> define CN.Test HourCounter myDummy:1 myDummy:0</pre><br />
<br />
Die neue Instanz weist folgende Struktur auf:<br />
<br />
<pre><br />
Internals:<br />
CFGFN <br />
DEF SHUTTER.JOHN:onoff:.1 SHUTTER.JOHN:onoff:.0<br />
NAME CN.Test<br />
NR 738<br />
NTFY_ORDER 50-CN.Test<br />
STATE 1<br />
TYPE HourCounter<br />
Readings:<br />
2014-02-04 20:59:22 clearDate 2014-02-04 20:59:22<br />
2014-02-04 20:59:57 countsOverall 1<br />
2014-02-04 20:59:57 countsPerDay 1<br />
2014-02-04 20:59:57 pauseTimeIncrement 35<br />
2014-02-04 20:59:57 pauseTimeOverall 35<br />
2014-02-04 20:59:57 pauseTimePerDay 35<br />
2014-02-04 21:00:01 pulseTimeIncrement 4<br />
2014-02-04 21:00:01 pulseTimeOverall 4<br />
2014-02-04 21:00:01 pulseTimePerDay 4<br />
2014-02-04 20:59:57 state 1<br />
2014-02-04 21:00:00 tickHour 1<br />
2014-02-04 21:00:01 value 0<br />
Helper:<br />
OFF_Regexp SHUTTER.JOHN:onoff:.0<br />
ON_Regexp SHUTTER.JOHN:onoff:.1<br />
calledByEvent <br />
changedTimestamp 2014-02-04 21:00:01<br />
forceClear <br />
forceDayChange <br />
forceHourChange <br />
forceMonthChange <br />
forceWeekChange <br />
isFirstRun <br />
sdRoundHourLast 1391544000<br />
value 0<br />
cmdQueue:<br />
</pre><br />
<br />
Damit nicht zu viele Ereignisse in den Log-Dateien landen, kann man diese sinnvoll einschränken, so daß<br />
nur Änderungen das Feuern von Events auslösen<br />
<pre><br />
attr CN.BRENNER event-on-change-reading .*<br />
</pre><br />
Wenn sich nun jedoch über Stunden und Tage nichts ändert, sieht man in den Charts keine Daten mehr.<br />
Mit dieser Anweisung wird erreicht, daß alle Readings nach Aktualisierung spätesten nach 1 Stunden einen Event feuern, auch wenn sich der Wert nicht ändert.<br />
Eine Ausnahme hiervon sollen machen die tick*-Readings, deren Events sollen immer sofort gefeuert werden, <br />
wenn sie aktualisiert werden.<br />
<pre><br />
attr CN.BRENNER event-min-interval tick.*:0,.*:3600<br />
</pre><br />
<br />
== Readings ==<br />
{| class="wikitable sortable"<br />
|-<br />
! Reading !! class="unsortable" | Beschreibung <br />
|-<br />
| clearDate || Datum, zu dem alle kumulativen Readings über set .. clear gelöscht wurden<br />
|-<br />
| countsOverall || Absolutzähler für das Auftreten des ON-Ereignisses<br />
|-<br />
| countsPerDay || Tageszähler für das Auftreten des ON-Ereignisses<br />
|-<br />
| pauseTimeIncrement || Zeitdauer der Pausen-Phase in Sekunden ; wird zyklisch aktualisiert<br />
|-<br />
| pauseTimeEdge || Zeitdauer der letzten komplettierten Pausen-Phase<br />
|-<br />
| pauseTimeOverall || Zeitdauer in Sekunden über alle aufgetretenen Pause-Phasen<br />
|-<br />
| pauseTimePerDay || Zeitdauer in Sekunden über alle aufgetretenen Pause-Phasen des akt. Tages<br />
|-<br />
| pulseTimeIncrement || Zeitdauer der Puls-Phase in Sekunden ; wird zyklisch aktualisiert<br />
|-<br />
| pulseTimeEdge || Zeitdauer der letzten komplettierten Pulse-Phase<br />
|-<br />
| pulseTimeOverall || Zeitdauer in Sekunden über alle aufgetretenen Puls-Phasen<br />
|-<br />
| pulseTimePerDay || Zeitdauer in Sekunden über alle aufgetretenen Puls-Phasen des akt. Tages<br />
|-<br />
| value || Aktueller Schaltzustand gemäss ON/OFF Ereignis,<br /><br />
mit 1=letztes Ereignis war ON-Ereignis <br /><br />
und 0=letztes Ereignis war OFF-Ereignis<br />
|-<br />
| tickUpdated || Event wird gefeuert, wenn die operativen Readings beschrieben worden sind (nicht unbedingt gändert)<br />
|-<br />
| tickChanged || Event wird nach Änderung von value gefeuert<br />
|-<br />
| tickDay || Event wird nach Tageswechsel gefeuert bevor die Tageszähler resettiert werden<br />
|-<br />
| tickHour || Event wird nach Stundenwechsel gefeuert<br />
|-<br />
| tickWeek || Event wird nach Wochenwechsel gefeuert<br />
|-<br />
| tickMonth || Event wird nach Monatswechsel gefeuert<br />
|-<br />
| tickYear || Event wird nach Jahreswechsel gefeuert<br />
|-<br />
| forceHourChange || simuliert einen Stundenwechsel<br />
|-<br />
| forceDayChange || simuliert einen Tageswechsel<br />
|-<br />
| forceWeekChange || simuliert einen Wochenwechsel<br />
|-<br />
| forceMonthChange || simuliert einen Monatswechsel<br />
|-<br />
| forceYearChange || simuliert einen Jahreswechsel<br />
|}<br />
<br />
== Web-Oberfläche ==<br />
[[Datei:13_11_15_HourCounter_Face.png|HourCounter]]<br />
<br />
== Anwendung ==<br />
Nachfolgende Darstellung zeigt das Einschaltverhalten eines Heizungskessels zusammen mit den <br />
abgeleiteten Werten.<br />
<br />
[[Datei:13_11_15_HourCounter_Chart.png]]<br />
<br />
* die Kurve "Brenner EIN" zeigt die Trigger-Signale des ON/OFF Filters, also das Ein-/Ausschalten des Brenners<br />
* die Kurve "Brenner-Starts" zeigt die über den Tag aufgelaufenen Starts, also chronologisch das Anwachsen von Reading countsPerDay<br />
* die Kurve "Betriebsstunden" zeigt die aufgelaufene Zeit aus dem Reading pulseTimePerDay umgerechnet zu Stunden<br />
* die Kurve "Dauer" zeigt die Dauer des letzten Pulses in Sekunden<br />
* die Kurve Auslastung zeigt das Verhältnis des Readings pulseTimePerDay zur seit Tagesbeginn vergangenen Zeit.<br />
<br />
=== Chart der Aktualwerte ===<br />
Wir benötigen hierzu ein File-Archiv für die aufgelaufenen Daten.<br />
<pre>define CN.Test.File FileLog ./log/CN.Test-%Y.log (CN\.Test:.*)</pre><br />
Man erhält nach den ersten Ereignissen Einträge in folgender Form:<br />
<pre><br />
2013-11-16_12:45:40 CN.Test value: 1<br />
2013-11-16_12:45:40 CN.Test 1<br />
2013-11-16_12:46:21 CN.Test pulseTimeIncrement: 41 <br />
2013-11-16_12:46:21 CN.Test pulseTimePerDay: 41<br />
2013-11-16_12:46:21 CN.Test pulseTimeOverall: 41<br />
2013-11-16_12:46:21 CN.Test value: 0<br />
2013-11-16_12:50:38 CN.Test countsPerDay: 2<br />
2013-11-16_12:50:38 CN.Test countsOverall: 2<br />
2013-11-16_12:50:38 CN.Test pauseTimeIncrement: 257<br />
2013-11-16_12:50:38 CN.Test pauseTimePerDay: 756<br />
2013-11-16_12:50:38 CN.Test pauseTimeOverall: 756<br />
2013-11-16_12:50:38 CN.Test value: 1<br />
2013-11-16_12:50:38 CN.Test 2<br />
</pre><br />
<br />
Nun kann man den Chart definieren:<br />
[[Datei:13_11_16_HourCounter_ChartBuild_01.png]]<br />
<br />
Für die Kurve "Brenner EIN" verwenden wir CN.Test.value. Damit diese als unterste Kurve dargestellt wird transformieren wir den Wert 1 auf -2 und alle anderen (also die 0) auf -21 mit folgender Funktion:<br />
<pre>$fld[3]=~"1"?-2:-19</pre><br />
Die "Brenner Starts" können wir direkt von countsPerDay ableiten.<br /><br /><br />
Für die "Betriebsstunden" verwenden wir pulseTimePerDay. Da diese in Sekunden vorliegen teilen wir den Wert durch 3600, um Stunden zu erhalten.<br />
<pre>$fld[3]/=3600</pre><br />
Als letzten versorgen wir noch die Kurve "Dauer" mit pulseTimeIncrement. Da wir diese in Minuten haben wollen ist ebenfalls eine Umformung nötig.<br />
<pre>$fld[3]/=60</pre><br />
<br />
Somit sind die Basis-Kurven angelegt.<br />
<br />
=== Erweiterungen ===<br />
Es wurden im Forum viele Wünsche formuliert, weitere Funktionalitäten für den HourCounter einzuführen.<br />
<br />
* Aggregation über bestimmte oder ganz freie Zeiträume<br />
* komplexe Berechnungen, die zum Verbrauch führen<br />
* Zuordnung von Verbräuchen zu unterschiedlichen Countern nach bestimmten Bedingungen<br />
<br />
Vor allem die Aggregation erfasster Werte in Stunden-, Tages-, Wochen- und Monatswerten ist eine sinnvolle<br />
Erweiterung bei der Verbrauchserfassung.<br />
<br />
HourCounter bietet Schnittstellen an, die es ermöglichen, das Modul selbst mit neuen Eigenschaften zu erweitern.<br />
<br />
Die Referenz-Implementierung in 99_UtilsHourCounter.pm zeigt, wie dies skript-technisch zu realisieren ist.<br />
<br />
==== Installation ====<br />
Die jeweils aktuelle Version von 99_UtilsHourCounter kann über diesen <br />
[http://sourceforge.net/p/fhem/code/HEAD/tree/trunk/fhem/contrib/99_UtilsHourCounter.pm?format=raw Link] bezogen werden.<br />
<br />
==== Readings ====<br />
99_UtilsHourCounter erweitert den HourCounter um folgende Funktionen:<br />
{| class="wikitable sortable"<br />
|-<br />
! Reading !! class="unsortable" | Beschreibung<br />
|-<br />
| appCountsPerHour || Stundenzähler, wird bei Stundenwechsel aktualisiert<br />
|-<br />
| appCountsPerHourTemp || Arbeitszähler zu appCountsPerHour<br />
|-<br />
| appCountsPerDay || Tageszähler, wird bei Tageswechsel aktualisiert (Arbeitszähler ist countsPerDay)<br />
|-<br />
| appCountsPerWeek || Wochenzähler, wird bei Wochenwechsel aktualisiert<br />
|-<br />
| appCountsPerWeekTemp || Arbeitszähler zu appCountsPerWeek<br />
|-<br />
| appCountsPerMonth || Monatszähler, wird bei Monatswechsel aktualisiert<br />
|-<br />
| appCountsPerMonthTemp || Arbeitszähler zu appCountsPerMonth<br />
|-<br />
| appCountsPerYear || Jahreszähler, wird bei Jahreswechsel aktualisiert<br />
|-<br />
| appCountsPerYearTemp || Arbeitszähler zu appCountsPerYear<br />
|-<br />
| appOpHoursPerDay || Betriebsstunden des Tages<br />
|-<br />
| appOpHoursPerDayTemp || Arbeitszähler zu appOpHoursPerDay<br />
|-<br />
| appOpHoursPerWeek || Betriebsstunden der Woche<br />
|-<br />
| appOpHoursPerWeekTemp || Arbeitszähler zu appOpHoursPerWeek<br />
|-<br />
| appOpHoursPerMonth || Betriebsstunden des Monats<br />
|-<br />
| appOpHoursPerMonthTemp || Arbeitszähler appOpHoursPerMonth<br />
|-<br />
| appOpHoursPerYear || Betriebsstunden des Jahres<br />
|-<br />
| appOpHoursPerYearTemp || Arbeitszähler appOpHoursPerYear<br />
|-<br />
| appUtilization || Auslastung = pulseTimePerDay /(vergangene Sekunden seit Tagesbeginn) * 100<br />
|-<br />
| appUtilizationTemp || Arbeitsvariable zu appUtilization<br />
|}<br />
Beginn der Woche ist jeweils der Sonntag.<br /><br />
<br />
Mit folgender Anweisung aktivieren wir die Erweiterungen: <br />
:<code><br />
define CN.EVENT notify CN\..*:tick.* { appHCNotify("%NAME","%EVTPART0","%EVTPART1");;}<br />
</code><br />
<br />
Spätestens nach einer steigenden und einer fallenden Flanke sind die zuvor genannten app*-Readings zu sehen.<br />
{{Randnotiz|RNTyp=Info|RNText=Die gezeigten define-Anweisungen müssen jeweils in einer Zeile stehen (keine Zeilenumbrüche!).}}<br />
<br />
Die neuen Readings werden automatisch in den "Setter" der Web-Oberflächen aufgenommen. Dies gilt für alle Readings, die mit "app" beginnen.<br />
<br />
Somit können die neuen Readings beliebig manipuliert werden.<br />
<br />
==== Archiv für Tages-/Wochen-/Monats-/Jahreswerte anlegen ====<br />
Nun wollen wir die aggregierten Werte in eine eigene Datei speichern. Dies gelingt mit <br />
:<code>define CN.Test.FileDay FileLog ./log/CN.Test-Day-%Y.log CN.Test:app\w+ (Utilization|PerHour|PerDay|PerWeek|PerMonth|PerYear)(?!Temp).* </code><br />
<br />
<br />
Im Klartext:<br />
* verwende alle Werte des Counters CN.Test, deren Reading mit "app" beginnt<br />
* und die einen der Terme appUtilization|PerHour|PerDay|PerWeek|PerMonth|PerYear beinhalten<br />
* und die danach nicht dem Term "Temp" beinhalten<br />
<br />
== Fragen und Antworten ==<br />
==== Betriebsstundenzähler über Leistungsmessung ableiten ====<br />
'''Frage:'''<br />
Ich würde gerne zählen, wenn ich mehr Strom als Standy verbrauche (also mehr als 2Watt)<br />
und keine Betriebsstunden zählen, wenn der Verbrauch unter 2 Watt ist. Ist das möglich?<br />
<br />
<u>Beispiel für die Events</u><br />
<pre><br />
013-11-18_19:40:32 XXX power: 1.9<br />
2013-11-18_19:40:32 XXX consumption: 2<br />
2013-11-18_19:40:32 XXX consumptionTotal: 2<br />
2013-11-18_19:40:36 XXX power: 27<br />
2013-11-18_19:40:36 XXX consumption: 2<br />
2013-11-18_19:40:36 XXX consumptionTotal: 2<br />
2013-11-18_19:40:42 XXX power: 34.6<br />
2013-11-18_19:40:42 XXX consumption: 2 <br />
</pre><br />
<br />
'''Antwort'''<br />
Die hier vorgestellte Lösung überprüft ob der Wert des Events power eine oder zwei Ziffern vor dem Komma hat.<br />
Deshalb wir hier erst gezählt, wenn die Schwelle von 10Watt überschritten wird. <br />
<pre><br />
define CN.Test HourCounter XXX:power:\s[0-9]{2,}(\.[0-9]{1,3})*$ XXX:power:\s[0-9]{1}(\.[0-9]{1,3})*$<br />
</pre><br />
Erläuterung zu <regexp_for_ON> = XXX:power:\s[0-9]{2,}(\.[0-9]{1,3})*$<br />
* "XXX" bezeichnet das Device, der Term danach ist der regexp-Filte für das On-Ereignis<br />
* "power:" das Ereignis muss mit diesem Term beginnen<br />
* "\s" es muss ein Leerzeichen folgen<br />
* "[0-9]{2,}" es müssen mindestens 2 Ziffern folgen<br />
* "(\.[0-9]{1,3})*" wenn ein Punkt folgt, dann müssen auf diesen mindestens 1..3 Ziffern folgen<br />
* "$" danach darf kein weiteres Zeichen mehr folgen<br />
<br />
'''Antowrt Möglichkeit 2'''<br />
In dieser Lösung bekommt das entsprechende Device was mit HourCounter überwacht werden soll ein userReadings "onoff". Dieses Reading wird dann zum Schalten von Hour Counter verwendet:<br />
<pre><br />
define GPIO4_DS18B20_Waermepumpe_Vorlauf GPIO4 28-000005956079<br />
attr GPIO4_DS18B20_Waermepumpe_Vorlauf alias Wärmepumpe Vorlauf<br />
attr GPIO4_DS18B20_Waermepumpe_Vorlauf model DS18B20<br />
attr GPIO4_DS18B20_Waermepumpe_Vorlauf room GPIO4<br />
attr GPIO4_DS18B20_Waermepumpe_Vorlauf userReadings onoff {(ReadingsVal("GPIO4_DS18B20_Waermepumpe_Vorlauf","temperature",0) >28)?1:0;;}<br />
<br />
define Waermepumpe_HourCounter HourCounter GPIO4_DS18B20_Waermepumpe_Vorlauf:onoff:.1 GPIO4_DS18B20_Waermepumpe_Vorlauf:onoff:.0<br />
attr Waermepumpe_HourCounter room 2_Fussbodenheizung<br />
</pre><br />
Erläuterungen zu dem Code: "{(ReadingsVal("GPIO4_DS18B20_Waermepumpe_Vorlauf","temperature",0) >28)?1:0;;}"<br />
* "(ReadingsVal("GPIO4_DS18B20_Waermepumpe_Vorlauf","temperature",0) >28)" Diese Bedingung für das userReadings onoff prüft bei jedem Event, ob der Wert von temperature größer als 28 ist. <br />
* "?1:0" Ist dies der Fall wird das userReading onoff auf 1 gesetzt andernfalls auf 0.<br />
Auf Basis deses UserReadings wird dann der HourCounter definiert:<br />
* "GPIO4_DS18B20_Waermepumpe_Vorlauf:onoff:.1" Einschaltbedingung für HourCounter<br />
* "GPIO4_DS18B20_Waermepumpe_Vorlauf:onoff:.0" Abschaltbedingung für Hour Counter<br />
<br />
==== Welche Anwendungsfälle sind denkbar ? ====<br />
[http://forum.fhem.de/index.php/topic,12216.msg175163.html#msg175163 Aus dem Forum]<br />
* Betriebsstundenzähler für meine "Fliegenkiller-Steckdose"<br />
* Nutzungsdauer beschränken für TV,Internet oder Spielkonsolen für entnervte Eltern<br />
* Nutzungsdauer ermitteln zur Energieeinsparung (Klimageräte, Ventilatoren, Dunstabzugshauben etc.)<br />
* Lüftungsverhalten ermitteln (wie lange Fenster pro Tag geöffnet)<br />
<br />
[http://forum.fhem.de/index.php/topic,12216.msg195358.html#msg195358 Brenner Starts/Verbrauch + akkumulierte Werte]<br />
<br />
[http://forum.fhem.de/index.php/topic,12216.msg196358.html#msg196358 Ölverbrauch+Solar-Ladung]<br />
<br />
[http://voizchat.de/gaszaehler-verbrauch-erfassen-mit-fhem-und-raspberry-gpio/ Gaszähler mit HourCounter realisieren]<br />
==== Seltene Schaltvorgänge ====<br />
Die Schaltvorgänge sind über den Tag sehr wenige. Die Aktualisierung erfolgt immer erst<br />
bei der negativen Flanke. Wie kann man eine häufigere Aktualisierung erreichen ?<br />
<br />
'''Antwort'''<br />
<br />
Ab Version 1.0.0.6 ist wurde das Attribut "interval" eingeführt; es ist auf 60 Minuten voreingestellt und kann von 5..60 im 5 Minuten-Raster festgelegt werden.<br />
Es bestimmt, nach welcher Zeit Puls-/Pausendauer aktualisiert werden sollen, unabhängig vom Auftreten einer Schaltflanke.<br />
<br />
==== Korrekte Darstellung der akkumulierten Daten im Chart ====<br />
'''Frage'''<br />
<br />
"appCountsPerDay: 4" bezieht sich auf die Counts des Tages 2014-06-16, trägt aber selbst den Zeitstempel 2014-06-17 und wird demnach in einem Chart auch über den Tag " 2014-06-17" dargestellt.<br />
Das Problem betrifft alle akkumulierten Daten des HourCounters.<br />
Wie erreicht man im Chart die korrekte Darstellung ?<br />
<br />
<br />
'''Antwort'''<br />
<br />
Das Thema wurde [http://forum.fhem.de/index.php/topic,12216.msg239929.html#msg239929 hier] disktuiert.<br />
Eine Lösung findet man mit [http://www.fhemwiki.de/wiki/LogProxy LogProxy].<br />
Damit läßt sich ein negativer Offset für die X-Achse definieren, so daß die Daten wieder korrekt dargestellt werden.<br />
<br />
[[Kategorie:MAX]]<br />
[[Kategorie:HOWTOS]]</div>Albatros