logFromArray

Aus FHEMWiki

Häufig besteht in FHEM das Problem, dass man in einem Plot, dargestellt durch das SVG-Modul, zusätzliche Daten darstellen möchte. Das Hilfsmodul logProxy bietet dafür u.a. die Möglichkeit, eine horizontale oder vertikale Linie zu ziehen.

Basiscode

Mit dem folgenden Code kann man nun beliebige Array-Daten aus irgendeinem Device passgenau so umwandeln, dass sie in einen Plot eingefügt werden können. Das Perl-Programm logFromArray wird mit mehreren Parametern aufgerufen:

  • filename - der Name der zu beschreibenden Log-Datei
  • device - Der Name des FHEM-Device, welches die zu schreibenden Array-Daten enthält
  • reading - Der Name des Readings, in welchem die zu schreibenden Array-Daten liegen. Diese Daten müssen in Form einer durch einen Separator (z.B. ein Komma) getrennten Liste vorliegen.
  • optional: fcsep - Ein String, der den Separator enthält (default=Komma)
  • optional: fcday - Eine Zahl, die angibt, wie viele Tage in die Zukunft das Log verschoben werden soll. 0=default heißt, die Log-Datei wird für den heutigen Tage angelegt, 1 bedeutet den folgenden Tag, etc.
  • optional: fcstart - Eine Uhrzeitangabe, wann das Log beginnen soll. 00:00 ist der Default-Wert
  • optional: fctype - 0=default oder 1. Wenn man die configDB verwendet, schreibt FHEM Daten gerne in diese. Um das zu vermeiden, kann mit einem Wert 1 erzwungen werden, dass das Logfile im Dateisystem landet.

Hier nun zunächst der Code, dieser sollte in eine Utils-Datei eingefügt werden, z.B. in die 99_myUtils.pm. Nicht vergessen, diese neu zu laden. (Klicke auf "Ausklappen", um die Anzeige auszuklappen)

Code:
###############################################################################
#
#  Log file format from array
#  
#  Parameter: filename:obvious, isn't it? 
#             device:  FHEM device for reading data
#             reading: Name of the reading containing a data array
#      optional
#             fcsep:   separator for the array, default= ","
#             fcres:   time resolution for the array in minutes, 1..1440;
#                      deafult= 60
#                      ==> Determines the number of expected values
#             fcday:   0=today=default,1=tomorrow,2=day after tomorrow, etc.
#             fcstart: starting time for log xx:xx, default= 00:00
#             fctype:  0=default or 1. In case of using configDB, a value
#                      of 1 enforces writing to the file system instead of the 
#                      configdb
#
###############################################################################

sub logFromArray($$$;$$$$$){
  my ($filename,$device,$reading,$fcsep,$fcres,$fcday,$fcstart,$fctype) = @_;
  
  #-- get data
  my $raw = ReadingsVal($device,$reading,undef);
  if( !$raw){
    Log 1,"[logFromArray] no data available in reading $reading of device $device";
    return
  }
  
  #-- default values
  $fcsep = ","
    if(!$fcsep);
  $fcres = 60
    if(!$fcres);
  $fcday = 0
    if(!defined($fcday));
  $fcstart = "00:00"
    if(!defined($fcstart));
  $fctype = 1
    if(!defined($fctype));
    
  #-- check data
  my $num = int(1440/$fcres);
  if( $num < 1 || $num > 1440){
    Log 1,"[logFromArray] invalid time resolution, must be 1..1440";
    return
  }
  
  if( $fcstart !~ /(\d\d):(\d\d)/){
    Log 1,"[logFromArray] improper start time specification, must be xx:xx with x=digit";
    return
  }
  my $lhour=$1;
  my $lmin=$2;  
  
  my @data = split($fcsep,$raw);
  if( int(@data)!=$num){
    Log 1,"[logFromArray] invalid data number in reading $reading of device $device, expecting $num values sepatated by $fcsep";
    return
  }
  
  #-- prepare output
  my @ldata=();
  my $lstart=int(time-time%86400-7200)+$fcday*86400+$lhour*3600+$lmin*60;
  for (my $i=0;$i<$num;$i++){
    push(@ldata,strftime( "%Y-%m-%d_%H:%M:%S",localtime( $lstart+$i*$fcres*60))." $device $reading $data[$i]");
  }
  
  #-- write data
  my $ret;
  if( $fctype !=1 ){
    $ret=FileWrite($filename,@ldata);
  }else{
    my $param = { FileName => $filename, ForceType => "file", NoNL => 0 };
    $ret=FileWrite($param,@ldata);
  }
  return $ret
}

Einbindung in Plots

Im zweiten Schritt muss man dafür sorgen, dass das Modul SVG diese Daten auch lesen kann. Dazu definiert man ein neues FileLog-Device, z.B.

define NEUESLOG FileLog <Dateiname> garnichts

Dieses FileLog lauscht also auf Events, die den String garnichts enthalten. Und hoffentlich kommen diese auch gar nicht vor... Wichtig ist, dass der angegebene Dateiname nicht die üblichen FHEM-spezifischen Platzhalter beinhaltet, es soll also nur genau eine Logdatei vorhanden sein.

In einem Plot wird nun einfach als Datensatz auf genau dieses FileLog verwiesen.

Beispiele

Regenvorhersage

Mit diversen Wettermodulen lassen sich die stündlichen Regenmengen für den heutigen bzw. folgende Tage abfragen. Für das Beispiel nehmen wir an, dass diese in Readings des Devices A.WS liegen, für den heutigen und den folgenden Tag:

rain_forecast0 0.2 0.2,0.2 0.4,0.3 0.7,0.6 1.3,0.7 2,0.8 2.8,0.2 3,0.2 3.2,0.2 3.4,0.6 4,0.7 4.7,0.7 5.4,0.5 5.9,0.3 6.2,0.4 6.6,0.3 6.9,0.3 7.2,0.4 7.6,0.6 8.2,0.7 8.9,0.6 9.5,0.5 10,0.5 10.5,0.3 10.8 2025-05-28 12:41:44
rain_forecast1 0.3 0.3,0.2 0.5,0.2 0.7,0.0 0.7,0.0 0.7,0.0 0.7,0.0 0.7,0.0 0.7,0.0 0.7,0.0 0.7,0.0 0.7,0.0 0.7,0.0 0.7,0.0 0.7,0.0 0.7,0.0 0.7,0.0 0.7,0.0 0.7,0.0 0.7,0.0 0.7,0.0 0.7,0.0 0.7,0.0 0.7,0.0 0.7  2025-05-28 12:00:06

In diesem Array ist jeweils der erste der beiden Werte die Regenmenge der vergangenen Stunde, der zweite Wert die kumulierte Regenmenge für den Tag. Wann man diese Werte "holt", ist eigentlich egal - man muss lediglich darauf achten, dass sich forecast0 immer auf den gegenwärtigen Tag bezieht. Es ist außerdem wichtig zu wissen, dass sich hier der erste Datenwert auf den Zeitraum von 0:00 - 1:00 bezieht. Es ist Geschmackssache, ob man das dann im Plot lieber ab 0:00 oder ab 1:00 darstellen möchte, dafür gibt es den Parameter fcstart.

Das fiktive FileLog-Device erstellt man mit

define rain_forecast0.FL FileLog /home/fhem/fhemlogs/rain_forecast0.log garnichts

Jetzt wird irgenwann zu einem beliebigen Zeitpunkt noch die Funktion logFromArray aufgerufen, und zwar mit den Parametern

logFromArray("/home/fhem/fhemlogs/rain_forecast0.log","A.WS","rain_forecast0",",",60,0,"01:00",1)}

Sinnvollerweise wird das immer genau dann erledigt, wenn die Vorhersagedaten geholt wurden.

Wie man aus den Readings oben sehen kann, ist das "heutige" Datum der 28.5.2025. Dem entsprechend erzeugt der Funktionsaufruf einen Inhalt der Logdatei

2025-05-28_01:00:00 A.WS rain_forecast0 0.2 0.2
2025-05-28_02:00:00 A.WS rain_forecast0 0.2 0.4
2025-05-28_03:00:00 A.WS rain_forecast0 0.3 0.7
... (Hier weitere Zeilen)
2025-05-28_20:00:00 A.WS rain_forecast0 0.7 8.9
2025-05-28_21:00:00 A.WS rain_forecast0 0.6 9.5
2025-05-28_22:00:00 A.WS rain_forecast0 0.5 10
2025-05-28_23:00:00 A.WS rain_forecast0 0.5 10.5
2025-05-29_00:00:00 A.WS rain_forecast0 0.3 10.8

Die Einbindung in einen Plot erfolgt durch die übliche Angabe des FileLog und der Plotparameter in dem Plot-Editor, oder in der .gplot-datei. Die entsprechende Zeile lautet dann in der .gplot-Datei z.B.

#rain_forecast0.FL 4:A.WS.rain_forecast0::{$fld[3]*10}

Rain forecast0.png