DbRep - Reporting und Management von DbLog-Datenbankinhalten: Unterschied zwischen den Versionen

Aus FHEMWiki
Zur Navigation springen Zur Suche springen
Keine Bearbeitungszusammenfassung
 
(148 dazwischenliegende Versionen von 6 Benutzern werden nicht angezeigt)
Zeile 3: Zeile 3:
|ModType=h
|ModType=h
|ModCmdRef=DbRep
|ModCmdRef=DbRep
|ModForumArea=Sonstiges
|ModForumArea=Automatisierung
|ModTechName=93_DbRep.pm
|ModTechName=93_DbRep.pm
|ModOwner=DS_Starter ({{Link2FU|16933|Forum}}/[[Benutzer Diskussion:DS Starter|Wiki]])}}
|ModOwner=DS_Starter ({{Link2FU|16933|Forum}}/[[Benutzer Diskussion:DS Starter|Wiki]])}}


== Zweck und Einsatz des Moduls ==
== Zweck und Einsatz des Moduls ==
 
Zweck des Moduls ist es, den Inhalt von [[DbLog]]-Datenbanken nach bestimmten Kriterien zu durchsuchen, zu managen, das Ergebnis hinsichtlich verschiedener Aggregationen auszuwerten und als Readings darzustellen. Die Abgrenzung der zu berücksichtigenden Datenbankinhalte erfolgt durch die Angabe von Device, Reading und die Zeitgrenzen für Auswertungsbeginn bzw. Auswertungsende.
Zweck des Moduls ist es, den Inhalt von DbLog-Datenbanken nach bestimmten Kriterien zu durchsuchen, zu managen, das Ergebnis hinsichtlich verschiedener Aggregationen auszuwerten und als Readings darzustellen. Die Abgrenzung der zu berücksichtigenden Datenbankinhalte erfolgt durch die Angabe von Device, Reading und die Zeitgrenzen für Auswertungsbeginn bzw. Auswertungsende.


Alle Datenbankoperationen werden nichtblockierend ausgeführt. Die Ausführungszeit der (SQL)-Hintergrundoperationen kann optional ebenfalls als Reading bereitgestellt werden (siehe Attribute).
Alle Datenbankoperationen werden nichtblockierend ausgeführt. Die Ausführungszeit der (SQL)-Hintergrundoperationen kann optional ebenfalls als Reading bereitgestellt werden (siehe Attribute).
Alle vorhandenen Readings werden vor einer neuen Operation gelöscht. Durch das Attribut "readingPreventFromDel" kann eine Komma separierte Liste von Readings angegeben werden die nicht gelöscht werden sollen.
Alle vorhandenen Readings werden vor einer neuen Operation gelöscht. Durch das Attribut "readingPreventFromDel" kann eine Komma separierte Liste von Readings angegeben werden die nicht gelöscht werden sollen.


'''Zur Zeit werden folgende Operationen unterstützt:'''
'''Zur Zeit ist das Modul durch folgende Leistungsmerkmale gekennzeichnet:'''


* Selektion aller Datensätze innerhalb einstellbarer Zeitgrenzen.
* Selektion und Anzeige aller Datensätze innerhalb einstellbarer Zeitgrenzen.
* Darstellung der Datensätze einer Device/Reading-Kombination innerhalb einstellbarer Zeitgrenzen.
* Darstellung der Datensätze einer Device/Reading-Kombination innerhalb einstellbarer Zeitgrenzen.
* Selektion der Datensätze unter Verwendung von dynamisch berechneter Zeitgrenzen zum Ausführungszeitpunkt.
* Selektion der Datensätze unter Verwendung von dynamisch berechneter Zeitgrenzen zum Ausführungszeitpunkt.
* Dubletten-Hervorhebung bei Datensatzanzeige (fetchrows)
* Berechnung der Anzahl von Datensätzen einer Device/Reading-Kombination unter Berücksichtigung von Zeitgrenzen und verschiedenen Aggregationen.
* Berechnung der Anzahl von Datensätzen einer Device/Reading-Kombination unter Berücksichtigung von Zeitgrenzen und verschiedenen Aggregationen.
* Die Berechnung von Summen- , Differenz- , Maximum- , Minimum- und Durchschnittswerten von numerischen Readings in Zeitgrenzen und verschiedenen Aggregationen.
* Die Berechnung von Summen-, Differenz-, Maximum-, Minimum- und Durchschnittswerten numerischer Readings in Zeitgrenzen und verschiedenen Aggregationen.
* Speichern von Summen-, Differenz- , Maximum- , Minimum- und Durchschnittswertberechnungen in der Datenbank
* Löschung von Datensätzen. Die Eingrenzung der Löschung kann durch Device und/oder Reading sowie fixer oder dynamisch berechneter Zeitgrenzen zum Ausführungszeitpunkt erfolgen.
* Löschung von Datensätzen. Die Eingrenzung der Löschung kann durch Device und/oder Reading sowie fixer oder dynamisch berechneter Zeitgrenzen zum Ausführungszeitpunkt erfolgen.
* Export von Datensätzen in ein File im CSV-Format.  
* Export von Datensätzen in ein File im CSV-Format.  
* Import von Datensätzen aus File im CSV-Format.
* Import von Datensätzen aus File im CSV-Format.
* Umbenennen von Device-Namen in Datenbanksätzen  
* Umbenennen von Device/Readings in Datenbanksätzen
* Änderung von gespeicherten Reading-Werten (VALUES) in der Datenbank (changeValue)  
* automatisches Umbenennen (Autorename) von Device-Namen in Datenbanksätzen und DbRep-Definitionen nach FHEM "rename" Befehl (siehe [[#DbRep_Agent_-_automatisches_.C3.84ndern_von_Device-Namen_in_Datenbanken_und_DbRep-Definitionen_nach_FHEM_.22rename.22 | DbRep-Agent]])  
* automatisches Umbenennen (Autorename) von Device-Namen in Datenbanksätzen und DbRep-Definitionen nach FHEM "rename" Befehl (siehe [[#DbRep_Agent_-_automatisches_.C3.84ndern_von_Device-Namen_in_Datenbanken_und_DbRep-Definitionen_nach_FHEM_.22rename.22 | DbRep-Agent]])  
* Ausführen von beliebigen Benutzer spezifischen SQL-Kommandos  
* Ausführen von beliebigen benutzerspezifischen SQL-Kommandos  
* Backups der FHEM-Datenbank erstellen (MySQL)
* Backups der FHEM-Datenbank im laufenden Betrieb erstellen (MySQL, SQLite) mit/ohne Komprimierung der Dumpfiles
* senden des Dumpfiles zu einem FTP-Server nach dem Backup
* senden und versionieren Dumpfiles nach dem Backup an einen FTP-Server
* Restore von serverSide-Backups (MySQL)
* Restore von SQLite-Dumps und MySQL serverSide-Backups  
* Optimierung der angeschlossenen Datenbank  
* Optimierung der angeschlossenen Datenbank (optimizeTables, vacuum)
* Ausgabe der existierenden Datenbankprozesse (MySQL)  
* Ausgabe der existierenden Datenbankprozesse (MySQL)  
* leeren der current-Tabelle
* leeren der current-Tabelle
* Auffüllen der current-Tabelle mit einem (einstellbaren) Extrakt der history-Tabelle
* Auffüllen der current-Tabelle mit einem (einstellbaren) Extrakt der history-Tabelle
 
* Bereinigung sequentiell aufeinander folgender Datensätze (sequentielle Dublettenbereinigung)
* Reparatur einer korrupten SQLite Datenbank ("database disk image is malformed")
* Übertragung von Datensätzen aus der Quelldatenbank in eine andere (Standby) Datenbank (syncStandby)
* Reduktion der Anzahl von Datensätzen in der Datenbank (reduceLog)
* Löschen von doppelten Datensätzen (delDoublets)
* Löschen und (Wieder)anlegen der für DbLog und DbRep benötigten Indizes (index)


Zur Aktivierung der Funktion "Autorename" wird dem definierten DbRep-Device mit dem Attribut "role" die Rolle "Agent" zugewiesen. Die Standardrolle nach Definition ist "Client". Mehr ist dazu im Abschnitt [[#DbRep_Agent_-_automatisches_.C3.84ndern_von_Device-Namen_in_Datenbanken_und_DbRep-Definitionen_nach_FHEM_.22rename.22 | DbRep-Agent]] beschrieben.  
Zur Aktivierung der Funktion "Autorename" wird dem definierten DbRep-Device mit dem Attribut "role" die Rolle "Agent" zugewiesen. Die Standardrolle nach Definition ist "Client". Mehr ist dazu im Abschnitt [[#DbRep_Agent_-_automatisches_.C3.84ndern_von_Device-Namen_in_Datenbanken_und_DbRep-Definitionen_nach_FHEM_.22rename.22 | DbRep-Agent]] beschrieben.  


DbRep stellt dem Nutzer einen UserExit zur Verfügung. Über diese Schnittstelle kann der Nutzer in Abhängigkeit von frei definierbaren Reading/Value-Kombinationen (Regex) eigenen Code zur Ausführung bringen. Diese Schnittstelle arbeitet unabhängig von einer Eventgenerierung. Weitere Informationen dazu ist unter [[#Attribute | Attribut]] "userExitFn" beschrieben.
DbRep stellt dem Nutzer einen UserExit zur Verfügung. Über diese Schnittstelle kann der Nutzer in Abhängigkeit von frei definierbaren Reading/Value-Kombinationen (Regex) eigenen Code zur Ausführung bringen. Diese Schnittstelle arbeitet unabhängig von einer Eventgenerierung. Weitere Informationen dazu ist unter [[#Attribute | Attribut]] "userExitFn" beschrieben.


FHEM-Forum:
FHEM-Forum:
Zeile 46: Zeile 51:


== Voraussetzungen und Abgrenzungen ==
== Voraussetzungen und Abgrenzungen ==
 
Das Modul setzt den Einsatz einer oder mehrerer DBLog-Instanzen voraus (bisher getestet mit PostgreSQL, MySQL und SQLite). Es werden die Zugangsdaten dieser Datenbankdefinition aus der Konfiguration des entsprechenden DbLog-Device genutzt.
Das Modul setzt den Einsatz einer oder mehrerer DBLog-Instanzen voraus (bisher getestet mit MySQL und SQLite). Es werden die Zugangsdaten dieser Datenbankdefinition aus der Konfiguration des entsprechenden DbLog-Device genutzt.
Es werden nur Inhalte der Tabelle "history" berücksichtigt (Ausnahme Kommando "sqlCmd").
Es werden nur Inhalte der Tabelle "history" berücksichtigt (Ausnahme Kommando "sqlCmd").


Überblick welche anderen Perl-Module DbRep verwendet:
Überblick, welche anderen Perl-Module DbRep verwendet:


::POSIX
::POSIX
Zeile 58: Zeile 62:
::DBI
::DBI
::Blocking (FHEM-Modul)
::Blocking (FHEM-Modul)
::Time::HiRes
::Encode
::Encode


Aus Performancegründen sollte zusätzlich folgender Index erstellt werden:
== Definition ==
 
<pre>
<pre>
  CREATE INDEX Report_Idx ON `history` (TIMESTAMP, READING) USING BTREE;
  define <name> DbRep <Name der DbLog-instanz>
</pre>
</pre>


Wenn nicht vorhanden das DBI-Modul installieren (z.B. unter Debian):
(*) '''<Name der DbLog-Instanz>:''' Es wird der Name der auszuwertenden DbLog-Datenbankdefinition angegeben, '''NICHT''' die Datenbank selbst.


sudo apt-get install libdbi-perl
Aus Performancegründen sollte der Index "Report_Idx" in der Datenbank (Tabelle history) angelegt sein. Ab der DbRep-Version 8.20.0 kann dieser Index, sowie die für DbLog benötigten Indizes, verwaltet werden.
Der Index "Report_Idx" wird einfach mit dem DbRep-Befehl:


<br>
== Definition ==
<pre>
<pre>
  define <name> DbRep <Name der DbLog-instanz>
  set <name> index recreate_Report_Idx
</pre>
</pre>


* <Name der DbLog-Instanz>: Es wird der Name der auszuwertenden DBLog-Datenbankdefinition angegeben, '''NICHT''' die Datenbank selbst.
auf der Datenbank angelegt. Ist er bereits vorhanden, wird er gelöscht und erneut angelegt.
 
<br>


== Set ==
== Set ==
Siehe {{Link2CmdRef|Lang=de|Anker=DbRep-set}}


Zur Zeit gibt es folgende "Set"-Kommandos. Über sie werden die Auswertungen angestoßen und definieren selbst die Auswertungsvariante.
== Get ==
Nach welchen Kriterien die Datenbankinhalte durchsucht werden und die Aggregation erfolgt, wird durch Atribute gesteuert.
Siehe {{Link2CmdRef|Lang=de|Anker=DbRep-get}}


* '''averageValue''' : berechnet den Durchschnittswert der Readingwerte (DB-Spalte "VALUE") in den gegebenen Zeitgrenzen ( siehe Attribute). Es muss das auszuwertende Reading über das Attribut "reading" angegeben sein.
== Attribute == 
Siehe {{Link2CmdRef|Lang=de|Anker=DbRep-attr}}


== DbRep Agent - automatisches Ändern von Device-Namen in Datenbanken und DbRep-Definitionen nach FHEM "rename" ==


* '''countEntries''' :  liefert die Anzahl der DB-Einträge in den gegebenen Zeitgrenzen ( siehe Attribute). Sind die Timestamps nicht gesetzt werden alle Einträge gezählt. Beschränkungen durch die Attribute Device bzw. Reading gehen in die Selektion mit ein.
Mit dem Attribut "role" wird die Rolle des DbRep-Device festgelegt. Die Standardrolle ist "Client". Mit der Änderung der Rolle in "Agent" wird das Device veranlasst auf Umbenennungen von Geräten in der FHEM Installation zu reagieren.


Durch den DbRep-Agenten werden folgende Features aktiviert wenn ein Gerät in FHEM mit "rename" umbenannt wird:


* '''delEntries'''  : löscht alle oder die durch die Attribute Device und/oder Reading definierten Datenbankeinträge. Die Eingrenzung über Timestamps erfolgt folgendermaßen:
* in der dem DbRep-Agenten zugeordneten Datenbank (Internal Database) wird nach Datensätzen mit dem alten Gerätenamen gesucht und dieser Gerätename in allen betroffenen Datensätzen in den neuen Namen geändert.


::* "timestamp_begin" gesetzt:  gelöscht werden DB-Einträge '''ab''' diesem Zeitpunkt bis zum aktuellen Datum/Zeit
* in dem DbRep-Agenten zugeordneten DbLog-Device wird in der Definition das alte durch das umbenannte Device ersetzt. Dadurch erfolgt ein weiteres Logging des umbenannten Device in der Datenbank.
::* "timestamp_end" gesetzt  :  gelöscht werden DB-Einträge '''bis''' bis zu diesem Zeitpunkt
::* beide Timestamps gesetzt :  gelöscht werden DB-Einträge '''zwischen''' diesen Zeitpunkten


::Aus Sicherheitsgründen muss das Attribut "allowDeletion" gesetzt sein um die Löschfunktion freizuschalten.
* in den existierenden DbRep-Definitionen vom Typ "Client" wird ein evtl. gesetztes Attribut "device = alter Devicename" in "device = neuer Devicename" geändert. Dadurch werden Auswertungsdefinitionen bei Geräteumbenennungen automatisch konsistent gehalten.  


Mit der Änderung in einen Agenten sind folgende Restriktionen verbunden, die mit dem Setzen des Attributes "role = Agent" eingeschaltet und geprüft werden:


* '''deviceRename''' : benennt den Namen eines Device innerhalb der angeschlossenen Datenbank (Internal DATABASE) um. Der Gerätename wird immer in der gesamten Datenbank umgesetzt. Eventuell gesetzte Zeitgrenzen oder Beschränkungen durch die Attribute Device bzw. Reading werden nicht berücksichtigt.
* es kann nur einen Agenten pro Datenbank in der FHEM-Installation geben. Ist mehr als eine Datenbank mit DbLog definiert, können ebenso viele DbRep-Agenten eingerichtet werden


::'''Beispiel''':
* mit der Umwandlung in einen Agenten wird nur noch das Set-Komando "renameDevice" verfügbar sein sowie nur ein eingeschränkter Satz von DbRep-spezifischen Attributen zugelassen. Wird ein DbRep-Device vom bisherigen Typ "Client" in einen Agenten geändert, werden evtl. gesetzte und nun nicht mehr zugelassene Attribute glöscht.


::set <name> deviceRename <alter Devicename>,<neuer Devicename>
Die Aktivitäten wie Datenbankänderungen bzw. Änderungen an anderen DbRep-Definitionen werden im Logfile mit verbose=3 protokolliert. Damit die renameDevice-Funktion bei großen Datenbanken nicht in ein timeout läuft, sollte das Attribut "timeout" entsprechend dimensioniert werden. Wie alle Datenbankoperationen des Moduls wird auch das Autorename nonblocking ausgeführt.


::Die Anzahl der umbenannten Device-Datensätze wird im Reading "device_renamed" ausgegeben.
'''Beispiel''' für die Definition eines DbRep-Device als Agent:
::Wird der umzubenennende Gerätename in der Datenbank nicht gefunden, wird eine WARNUNG im Reading "device_not_renamed" ausgegeben.
::Entsprechende Einträge erfolgen auch im Logfile mit verbose=3


* '''diffValue'''    : berechnet den Differenzwert eines Readingwertes (DB-Spalte "Value") in den Zeitgrenzen (Attribute) "timestamp_begin", "timestamp_end" bzw "timeDiffToNow / timeOlderThan". Es muss das auszuwertende Reading im Attribut "reading" angegeben sein. Diese Funktion ist z.B. zur Auswertung von Eventloggings sinnvoll, deren Werte sich fortlaufend erhöhen und keine Wertdifferenzen wegschreiben. Es wird immer die Differenz aus dem Value-Wert des ersten verfügbaren Datensatzes und dem Value-Wert des letzten verfügbaren Datensatzes innerhalb der angegebenen Zeitgrenzen/Aggregation gebildet, wobei ein Übertragswert der Vorperiode (Aggregation) zur darauf folgenden Aggregationsperiode berücksichtigt wird sofern diese einen Value-Wert enhtält. Dabei wird ein Zählerüberlauf (Neubeginn bei 0) mit berücksichtigt (vergleiche [[#Attribute | Attribut]] "diffAccept"). Wird in einer auszuwertenden Zeit- bzw. Aggregationsperiode nur ein Datensatz gefunden, kann die Differenz in Verbindung mit dem Differenzübertrag der Vorperiode berechnet werden. In diesem Fall kann es zu einer logischen Ungenauigkeit in der Zuordnung der Differenz zur Aggregationsperiode kommen. Deswegen wird eine Warnung im "state" sowie das Reading "less_data_in_period" mit einer Liste der betroffenen Perioden erzeugt. 
::'''Hinweis:'''
::Im Auswertungs- bzw. Aggregationszeitraum (Tag, Woche, Monat, etc.) sollten dem Modul pro Periode mindestens ein Datensatz
::zu Beginn und ein Datensatz gegen Ende des Aggregationszeitraumes zur Verfügung stehen um eine möglichst genaue Auswertung
::der Differenzwerte vornehmen zu können.
* '''dumpMySQL [clientSide | serverSide]''' - erstellt einen Dump der angeschlossenen MySQL-Datenbank.
Abhängig von der ausgewählten Option wird der Dump auf der Client- bzw. Serverseite erstellt.
Die Varianten unterscheiden sich hinsichtlich des ausführenden Systems, des Erstellungsortes, der Attributverwendung, des erzielten Ergebnisses und der benötigten Hardwareressourcen.
Die Option "clientSide" benötigt z.B. eine leistungsfähigere Hardware des FHEM-Servers, sichert aber alle Tabellen inklusive eventuell angelegter Views.
'''Option clientSide'''
Der Dump wird durch den Client (FHEM-Rechner) erstellt und per default im log-Verzeichnis des Clients gespeichert. Das Zielverzeichnis kann mit dem Attribut "dumpDirLocal" verändert werden und muß auf dem Client durch FHEM beschreibbar sein.
Vor dem Dump kann eine Tabellenoptimierung ("optimizeTablesBeforeDump") oder ein FHEM-Kommando ("executeBeforeDump") optional zugeschaltet werden.
::'''Achtung !'''
::Um ein Blockieren von FHEM zu vermeiden, muß DbLog im asynchronen Modus betrieben werden wenn die Tabellenoptimierung verwendet wird !
Nach dem Dump kann ebenfalls ein FHEM-Kommando (siehe "executeAfterDump") ausgeführt werden.
Über weitere Attribute kann das Laufzeitverhalten der Funktion beeinflusst werden um eine Optimierung bezüglich Performance und Ressourcenbedarf zu erreichen.
Die für "dumpMySQL clientSide" relevanten Attribute sind "dumpComment", "dumpDirLocal", "dumpMemlimit", "dumpSpeed ", "dumpFilesKeep", "executeBeforeDump", "executeAfterDump" und "optimizeTablesBeforeDump".
Nach einem erfolgreichen Dump werden alte Dumpfiles gelöscht und nur die Anzahl "dumpFilesKeep" (default: 3) verbleibt im Zielverzeichnis "dumpDirLocal".
Die Namenskonvention der Dumpfiles ist: <dbname>_<date>_<time>.sql
Das erzeugte Dumpfile kann z.B. mit:
    mysql -u <user> -p <dbname> < <filename>.sql
auf dem MySQL-Server ausgeführt werden um die Datenbank aus dem Dump wiederherzustellen.
'''Option serverSide'''
Der Dump wird durch den MySQL-Server erstellt und per default im Home-Verzeichnis des MySQL-Servers gespeichert.
Es wird die gesamte history-Tabelle (nicht current-Tabelle) im CSV-Format ohne Einschränkungen exportiert.
Vor dem Dump kann eine Tabellenoptimierung ("optimizeTablesBeforeDump") optional zugeschaltet werden .
::'''Achtung !'''
::Um ein Blockieren von FHEM zu vermeiden, muß DbLog im asynchronen Modus betrieben werden wenn die Tabellenoptimierung verwendet wird !
Vor und nach dem Dump kann ein FHEM-Kommando (siehe "executeBeforeDump", "executeAfterDump") ausgeführt werden.
Die für "dumpMySQL serverSide" relevanten Attribute sind "dumpDirRemote", "dumpDirLocal", "dumpFilesKeep", "optimizeTablesBeforeDump", "executeBeforeDump" und "executeAfterDump".
Das Zielverzeichnis kann mit dem Attribut "dumpDirRemote" verändert werden. Es muß sich auf dem MySQL-Host gefinden und durch den MySQL-Serverprozess beschreibbar sein.
Der verwendete Datenbankuser benötigt das "FILE"-Privileg.
::'''Hinweis:'''
::Soll die interne Versionsverwaltung des Moduls genutzt und die Größe des erzeugten Dumpfiles ausgegeben werden, ist das Verzeichnis "dumpDirRemote" des MySQL-Servers ::auf dem Client zu mounten und im Attribut "dumpDirLocal" dem DbRep-Device bekannt zu machen.
<pre>
<pre>
    Beispiel:
define Rep.Agent DbRep LogDB
    attr <DbRep-device> dumpDirRemote /volume1/ApplicationBackup/dumps_FHEM/
attr Rep.Agent devStateIcon connected:10px-kreis-gelb .*disconnect:10px-kreis-rot .*done:10px-kreis-gruen
    attr <DbRep-device> dumpDirLocal /sds1/backup/dumps_FHEM/
attr Rep.Agent icon security
    attr <DbRep-device> dumpFilesKeep 2
attr Rep.Agent role Agent
 
attr Rep.Agent room DbLog
    # Der Dump wird remote auf dem MySQL-Server im Verzeichnis '/volume1/ApplicationBackup/dumps_FHEM/' erstellt.
attr Rep.Agent showproctime 1
    # Die interne Versionsverwaltung sucht im lokal gemounteten Verzeichnis '/sds1/backup/dumps_FHEM/' vorhandene Dumpfiles und löscht diese bis auf die zwei letzten   
attr Rep.Agent stateFormat { ReadingsVal("$name","state", undef) eq "running" ? "renaming" : ReadingsVal("$name","state", undef). " » ProcTime: ".ReadingsVal("$name","sql_processing_time", undef)." sec"}
      Versionen.
attr Rep.Agent timeout 86400
</pre>
</pre>


Wird die interne Versionsverwaltung genutzt, werden nach einem erfolgreichen Dump alte Dumpfiles gelöscht und nur die Anzahl "dumpFilesKeep" (default: 3) verbleibt im Zielverzeichnis "dumpDirRemote". FHEM benötigt in diesem Fall Schreibrechte auf dem Verzeichnis "dumpDirLocal".
== hilfreiche SQL Statements ==


Die Namenskonvention der Dumpfiles ist: <dbname>_<date>_<time>.csv
In dieser Rubrik werden SQL-Statements zusammengetragen, die User für ihre Auswertungen hilfreich fanden und anderen Anwendern zur Verfügung stellen.


Im Folgenden wird MySQL auch stellvertretend für MariaDB verwendet.


Die Statements können mit dem Befehl:


* '''exportToFile''' : exportiert DB-Einträge im CSV-Format in den gegebenen Zeitgrenzen. Einschränkungen durch die Attribute Device bzw. Reading gehen in die Selektion mit ein. Der Filename wird durch das Attribut "expimpfile" bestimmt.
set <DbRep> sqlCmd <SQL-Statement>


ausgeführt werden. Für die Anpassung der Ergebnisdarstellung ist das Attribut '''sqlResultFormat''' verfügbar.


* '''fetchrows'''    : liefert '''alle''' DB-Einträge in den gegebenen Zeitgrenzen ( siehe Attribute). Eine evtl. gesetzte Aggregation wird '''nicht''' berücksichtigt.  
'''Hinweis: <br> '''
Die Statements sind durch den DbRep-Modulautor nicht in jedem Fall getestet und dem Anwender obliegt vor Anwendung der Statements eine Datenbanksicherung durchzuführen um im Fehlerfall diese wieder herstellen zu können.


<br>
=== Den ersten und den letzten Wert eines Zeitraums selektieren bzw. deren Differenz (MySQL) ===
Beispiel 1:


* '''insert'''    : Manuelles Einfügen eines Datensatzes in die Tabelle "history". Obligatorisch sind Eingabewerte für Datum, Zeit und Value. Die Werte für die DB-Felder Type bzw. Event werden mit "manual" gefüllt, sowie die Werte für Device, Reading aus den gesetzten Attributen genommen.
Den ersten und letzten Wert der durch die DB-Variablen bestimmten Parameter ausgegeben.  
       
         
::'''Format:'''    Datum,Zeit,Value,[Unit] 
::Unit ist optional, Attribute "reading" und "device" müssen gesetzt sein.
::Soll "Value=0" eingefügt werden, ist "Value = 0.0" zu verwenden.  


::'''Beispiel:'''  2016-08-01,23:00:09,TestValue,TestUnit 
Verwendete DB-Variablen (@) : begin_time, end_time, device und reading
::die Feldlänge ist maximal 32 Zeichen lang, es sind KEINE Leerzeichen im Feldwert erlaubt !
SET @begin_time='2020-06-02 12:30:00';SET @end_time='2020-06-02 18:00:00';
SET @device='shelly02';SET @reading='energy_0';
   
   
::'''Hinweis:'''                   
SELECT TIMESTAMP,READING,round(VALUE/1000,0) AS VALUE
::Bei der Eingabe ist darauf zu achten dass im beabsichtigten Aggregationszeitraum (Tag, Woche, Monat, etc.) MINDESTENS
    FROM (
::zwei Datensätze für die Funktion diffValue zur Verfügung stehen. Ansonsten kann keine Differenz berechnet werden und
      (SELECT TIMESTAMP,READING,VALUE FROM history WHERE DEVICE = @device AND READING = @reading AND TIMESTAMP >= @begin_time and TIMESTAMP <= @end_time ORDER BY TIMESTAMP LIMIT 1)
::diffValue gibt in diesem Fall "0" in der betroffenen Periode aus !             
      UNION ALL
      (SELECT TIMESTAMP,READING,VALUE FROM history WHERE DEVICE = @device AND READING = @reading AND TIMESTAMP >= @begin_time and TIMESTAMP <= @end_time ORDER BY TIMESTAMP DESC LIMIT 1)
  ) AS X1;
+---------------------+----------+-------+
| TIMESTAMP          | READING  | VALUE |
+---------------------+----------+-------+
| 2020-06-02 12:31:01 | energy_0 |    69 |
| 2020-06-02 16:31:05 | energy_0 |    73 |
+---------------------+----------+-------+
Beispiel 2:


Berechnung der Differenz vom 1. Beispiel


* '''importFromFile''' : importiert Datensätze im CSV-Format aus einem File in die Datenbank. Der Filename wird durch das Attribut "expimpfile" bestimmt.
Verwendete DB-Variablen (@) : begin_time, end_time, device und reading


::Datensatzformat: "TIMESTAMP","DEVICE","TYPE","EVENT","READING","VALUE","UNIT"
Hierbei ist zu beachten, dass die einzelnen Selects vertauscht wurden um den TIMESTAMP des ersten Select zu bekommen.


::Die Felder "TIMESTAMP","DEVICE","TYPE","EVENT","READING" und "VALUE" müssen gesetzt sein. Das Feld "UNIT" ist optional.  
Die Subtraktion wurde durch "mal Minus Eins" des kleineren Wertes erreicht.<pre>
::Der Fileinhalt wird  als Transaktion importiert, d.h. es wird der Inhalt des gesamten Files oder, im Fehlerfall,
SET @begin_time='2020-06-02 12:30:00';SET @end_time='2020-06-02 18:00:00';
::kein Datensatz des Files importiert.
SET @device='shelly02';SET @reading='energy_0';


::Beispiel: "2016-09-25 08:53:56","STP_5000","SMAUTILS","etotal: 11859.573","etotal","11859.573",""
SELECT TIMESTAMP,READING,round(sum(VALUE)/1000,0) AS VALUE
  FROM (
    (SELECT TIMESTAMP,READING,VALUE FROM history WHERE DEVICE = @device AND READING = @reading AND TIMESTAMP >= @begin_time and TIMESTAMP <= @end_time ORDER BY TIMESTAMP DESC LIMIT 1)
    UNION ALL
    (SELECT TIMESTAMP,READING,(VALUE * -1) AS VALUE FROM history WHERE DEVICE = @device  AND  READING = @reading AND TIMESTAMP >= @begin_time and TIMESTAMP <= @end_time ORDER BY TIMESTAMP LIMIT 1)
  ) AS X1;


+---------------------+----------+-------+
| TIMESTAMP          | READING  | VALUE |
+---------------------+----------+-------+
| 2020-06-02 16:31:05 | energy_0 |    4 |
+---------------------+----------+-------+
</pre>
Die Formatierung


* '''maxValue'''    : berechnet den Maximalwert eines Readingwertes (DB-Spalte "VALUE") in den Zeitgrenzen (Attribute) "timestamp_begin", "timestamp_end" bzw. "timeDiffToNow / timeOlderThan". Es muss das auszuwertende Reading über das Attribut "reading" angegeben sein. Die Auswertung enthält den Zeitstempel des ermittelten Maximumwertes innerhalb der Aggregation bzw. Zeitgrenzen. Im Reading wird der Zeitstempel des '''letzten''' Auftretens vom Maximalwert ausgegeben falls dieser Wert im Intervall mehrfach erreicht wird.
<pre>
  round(sum(VALUE)/1000,0) AS VALUE
</pre>


sollte eventuell angepasst werden, da diese natürlich zum zu erwartenden Ergebnis passen muss.


* '''minValue''' : berechnet den Minimalwert eines Readingwertes (DB-Spalte "VALUE") in den Zeitgrenzen (Attribute) "timestamp_begin", "timestamp_end" bzw. "timeDiffToNow / timeOlderThan". Es muss das auszuwertende Reading über das Attribut "reading" angegeben sein. Die Auswertung enthält den Zeitstempel des ermittelten Minimumwertes innerhalb der Aggregation bzw. Zeitgrenzen. Im Reading wird der Zeitstempel des '''ersten''' Auftretens vom Minimalwert ausgegeben falls dieser Wert im Intervall mehrfach erreicht wird.
=== Wieviel PV-Leistung wird von einem Starkverbraucher verwendet (MySQL) ===
Hier wurde versucht aus der Datenbank zu ermitteln, wieviel PV-Leistung für z.B. eine Wärmepumpe übrig bleibt.
Dabei wurde der grundlegenden Hausverbrauch als erstes in Abzug gebracht und der Rest mit dem Starkverbraucher verrechnet.
Natürlich kann man solch einen Report nur für einen Verbraucher verwenden, da man ja den Überschuss nur einem zuordnen kann.
Auch wird hier mit einem Stundendurchschnitt gerechnet, was somit als Näherung angesehen werden muss.
<pre>
1) Zuerst werden die einzelnen Daten für einen Tag zusammen gesucht
2) Jeder Datenteil wird mit einem Durchschnitt auf Stundenbasis berechnet
3) Im JOIN werden alle Datenteile für die relevanten Stunden zusammen geführt. Maßgeblich hierbei ist der Betrieb des Starkverbrauchers
  zu dem Zeitpunkt wo auch PV-Leistung verfügbar war. Alle anderen Stunden tauchen danach nicht mehr auf. Ist es ein Hybrid WR mit
  Speicher, dann kann auch in der Nacht eine Unterstützung erfolgen, was im Winter jedoch recht selten sein wird.
4) Es wird der restliche Hausverbrauch ermittelt. Achtung, der Starkverbraucher ist ein Verbraucher, weshalb die Leistung im Zähler
  negativ summiert wird. Ein "+consumer_P" ist somit eine Summe mit einem negativen Wert ;-)
5) Im letzten SELECT werden dann die Daten final aufbereitet und formatiert, auch der prozentuale Anteil der PV-Leistung am gesamten
  Starkverbrauch wird noch berechnet.
</pre>
<pre>
consumer          = Das Zähler Device des Starkverbrauchers
consumer_P        = negative Leistungsanzeige in Watt, da es ein Verbraucher ist!
generator        = Das Wechselrichter Device
generator_P      = Die AC Ausgangsleistung in Watt
Home_consumtion_P = Der Hausverbrauch inklusive des Starkverbrauchers in Watt (ebenfalls im generator Device)
</pre>
Die verwendeten Variablen lassen sich im DbRep als Attribut setzen
<pre>
sqlCmdVars
SET @date:='2022-12-07', @consumer:='StromZaehler_Heizung', @consumer_P:='SMAEM1901401955_Saldo_Wirkleistung', @generator:='WR_1', @generator_P:='SW_Total_AC_Active_P', @Home_Consumtion_P:='SW_Home_own_consumption';
</pre>
Hier nun das SELECT Statement
<pre>
SET @date:='2022-12-07',
    @consumer:='StromZaehler_Heizung', @consumer_P:='SMAEM1901401955_Saldo_Wirkleistung',
    @generator:='WR_1', @generator_P:='SW_Total_AC_Active_P', @Home_Consumtion_P:='SW_Home_own_consumption';


SELECT
  X.HOUR,
  cast(X.generator_P AS decimal(7,2)) AS generator_P,
  cast(X.Home_Consumption AS decimal(7,2)) AS Home_Consumption,
  cast(X.PV_after_Home_Consumtion AS decimal(7,2)) AS PV_after_Home_Consumtion,
  cast(X.consumer_P AS decimal(7,2)) AS consumer_P,
  if(consumer_P+PV_after_Home_Consumtion <= 0,cast(round(abs(consumer_P+PV_after_Home_Consumtion),2) AS decimal(7,2)),0) AS from_Grid,
  if(round(consumer_P+PV_after_Home_Consumtion,2) <= 0,round(abs(PV_after_Home_Consumtion*100/consumer_P),0),100) AS Percent
FROM (
  SELECT
    X1.HOUR,
    generator_P,
    round(Home_Consumtion_P+consumer_P,2) AS Home_Consumption,
    if(Home_Consumtion_P+consumer_P-generator_P < 0,round(abs(Home_Consumtion_P+consumer_P-generator_P),2),0) AS PV_after_Home_Consumtion,
    consumer_P
  FROM (
    SELECT
      hour(TIMESTAMP) AS HOUR,
      round(avg(value),2) AS consumer_P
    FROM history
    WHERE
      TIMESTAMP > @date and TIMESTAMP < DATE_ADD(@date,INTERVAL 1 DAY) AND
      DEVICE = @Consumer AND
      READING = @consumer_P
    GROUP BY 1
    ) X1
  JOIN (
    SELECT
      hour(TIMESTAMP) AS HOUR,
      round(avg(value),2) AS generator_P
    FROM history
    WHERE
      TIMESTAMP > @date AND TIMESTAMP < DATE_ADD(@date,INTERVAL 1 DAY) AND
      DEVICE    = @generator AND
      READING  = @generator_P AND
      VALUE    > 10
    GROUP BY 1
    ) X2
  JOIN (
    SELECT
      hour(TIMESTAMP) AS HOUR,
      round(avg(value),2) AS Home_Consumtion_P
    FROM history
    WHERE
      TIMESTAMP > @date and TIMESTAMP < DATE_ADD(@date,INTERVAL 1 DAY) AND
      DEVICE  = @generator AND
      READING = @Home_Consumtion_P AND
      VALUE  > 10
    GROUP BY 1
    ) X3
  ON    X1.HOUR = X2.HOUR
    AND X1.HOUR = X3.HOUR
  ) X;
</pre>


* '''optimizeTables''' - optimiert die Tabellen in der angeschlossenen Datenbank (MySQL).
=== Temperaturdifferenz eines Pufferspeichers über die Zeit ermitteln (MySQL) ===
 
Beispiel 1:
::'''Hinweis:'''
::Obwohl die Funktion selbst non-blocking ausgelegt ist, muß das zugeordnete DbLog-Device im asynchronen Modus betrieben werden um ein Blockieren von FHEMWEB zu vermeiden.
 
 
* '''readingRename''' - benennt den Namen eines Readings innerhalb der angeschlossenen Datenbank (siehe Internal DATABASE) um. Der Readingname wird immer in der gesamten Datenbank umgesetzt. Eventuell gesetzte Zeitgrenzen oder Beschränkungen durch die Attribute Device bzw. Reading werden nicht berücksichtigt.
 
::'''Beispiel:'''


::set <name> readingRename <alter Readingname>,<neuer Readingname>
Temperaturdifferenz des Pufferspeichers über die Zeit in Minuten
::Die Anzahl der umbenannten Device-Datensätze wird im Reading "reading_renamed" ausgegeben.
::Wird der umzubenennende Readingname in der Datenbank nicht gefunden, wird eine WARNUNG im Reading "reading_not_renamed" 
::ausgegeben.
::Entsprechende Einträge erfolgen auch im Logfile mit verbose=3.


<nowiki>Referenz zum DBRep : set [device] sqlSpecial readingsDifferenceByTimeDelta</nowiki>


* '''sqlCmd''' - führt ein beliebiges Benutzer spezifisches Kommando aus.
Verwendete DB-Variablen (@) :  device und reading, diff und delta werden für Berechnungen benötigt und sind bei erneutem Aufruf zurück zu setzen
Enthält dieses Kommando eine Delete-Operation, muss zur Sicherheit das Attribut "allowDeletion" gesetzt sein.
Bei der Ausführung dieses Kommandos werden keine Einschränkungen durch gesetzte Attribute device und/oder reading berücksichtigt.
Sollen die im Modul gesetzten Attribute "timestamp_begin" bzw. "timestamp_end" im Statement berücksichtigt werden, können die Platzhalter "§timestamp_begin§" bzw. "§timestamp_end§" dafür verwendet werden.
 
::'''Beispiele für Statements:'''
 
::* set <name> sqlCmd select DEVICE, count(*) from history where TIMESTAMP >= "2017-01-06 00:00:00" group by DEVICE having count(*) > 800
::* set <name> sqlCmd select DEVICE, count(*) from history where TIMESTAMP >= "2017-05-06 00:00:00" group by DEVICE
::* set <name> sqlCmd select DEVICE, count(*) from history where TIMESTAMP >= §timestamp_begin§ group by DEVICE
::* set <name> sqlCmd select * from history where DEVICE like "Te%t" order by `TIMESTAMP` desc
::* set <name> sqlCmd select * from history where `TIMESTAMP` > "2017-05-09 18:03:00" order by `TIMESTAMP` desc
::* set <name> sqlCmd select * from current order by `TIMESTAMP` desc
::* set <name> sqlCmd select sum(VALUE) as 'Einspeisung am 04.05.2017', count(*) as 'Anzahl' FROM `history` where `READING` = "Einspeisung_WirkP_Zaehler_Diff" and TIMESTAMP between '2017-05-04' AND '2017-05-05'
::* set <name> sqlCmd delete from current
::* set <name> sqlCmd delete from history where TIMESTAMP < "2016-05-06 00:00:00"
::* set <name> sqlCmd update history set VALUE='TestVa$$ue$' WHERE VALUE='TestValue'
::* set <name> sqlCmd select * from history where DEVICE = "Test"
::* set <name> sqlCmd insert into history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES ('2017-05-09 17:00:14','Test','manuell','manuell','Tes§e','TestValue','°C')
::Das Ergebnis des Statements wird im Reading "SqlResult" dargestellt. Die Formatierung kann durch das Attribut "sqlResultFormat" ausgewählt werden.


::'''Hinweis:'''                   
Anmerkungen:
::Auch wenn das Modul bezüglich der Datenbankabfrage nichtblockierend arbeitet, kann eine zu große Ergebnismenge (Anzahl Zeilen bzw. Readings) die Browsersesssion bzw. FHEMWEB blockieren. Wenn man sich unsicher ist, sollte man vorsorglich dem Statement ein Limit hinzufügen.


- Der Zeitraum wird in diesem Beispiel als die letzten 24 Stunden festgelegt


* '''sumValue'''    :  berechnet die Summenwerte eines Readingwertes (DB-Spalte "VALUE") in den Zeitgrenzen (Attribute) "timestamp_begin", "timestamp_end" bzw. "timeDiffToNow / timeOlderThan". Es muss das auszuwertende Reading im Attribut "reading" angegeben sein. Diese Funktion ist sinnvoll wenn fortlaufend Wertedifferenzen eines Readings in die Datenbank geschrieben werden.
- Die Zeitdifferenz für die Änderung steht in der Spalte DELTA in Minuten


- In FHEM wurde mit event-on-change-reading ins DBLog geschrieben


- Bei dieser Ausgabe kann man erkennen, dass die Temperaturänderung im Wärmespeicher oft sehr klein ist, was ja auch so gewollt ist


* '''vacuum''' - optimiert die Tabellen in der angeschlossenen Datenbank (SQLite, PostgreSQL).
- Um 14:00 Uhr beginnt die Wärmepumpe das Warmwasser wieder aufzuheizen<pre>
SET @device='Heizung';SET @reading='hotWaterTemperature';
SET @diff=0;SET @delta=NULL;


::'''Hinweis:'''
SELECT t1.TIMESTAMP,t1.READING,t1.VALUE,t1.DIFF,t1.DELTA
::Obwohl die Funktion selbst non-blocking ausgelegt ist, muß das zugeordnete DbLog-Device im asynchronen Modus betrieben werden um ein Blockieren von FHEMWEB zu vermeiden.  
  FROM
    (
SELECT TIMESTAMP,READING,VALUE,
      if(@diff = 0,NULL, cast((VALUE-@diff) AS DECIMAL(3,1))) AS DIFF,
      @diff:=VALUE                                            AS curr_V,
      TIMESTAMPDIFF(MINUTE,@delta,TIMESTAMP)                  AS DELTA,
      @delta:=TIMESTAMP                                      AS curr_T
  FROM  history
  WHERE DEVICE    = @device  AND
        READING    = @reading AND
        TIMESTAMP >= NOW() - INTERVAL 1 DAY
  ORDER BY TIMESTAMP
    ) t1;
+---------------------+---------------------+-------+------+-------+
| TIMESTAMP          | READING            | VALUE | DIFF | DELTA |
+---------------------+---------------------+-------+------+-------+
| 2020-06-04 10:05:20 | hotWaterTemperature | 46.5  | NULL |  NULL |
| 2020-06-04 10:35:31 | hotWaterTemperature | 46.4  | -0.1 |    30 |
| 2020-06-04 11:00:31 | hotWaterTemperature | 46.2  | -0.2 |    25 |
snip...
| 2020-06-04 13:50:42 | hotWaterTemperature | 44.5  | -1.0 |    5 |
| 2020-06-04 13:55:42 | hotWaterTemperature | 44.0  | -0.5 |    5 |
| 2020-06-04 14:00:42 | hotWaterTemperature | 43.9  | -0.1 |    5 |
| 2020-06-04 14:10:42 | hotWaterTemperature | 41.7  | -1.8 |    5 |
snip...
| 2020-06-04 14:51:05 | hotWaterTemperature | 51.3  |  0.1 |    5 |
| 2020-06-04 17:01:15 | hotWaterTemperature | 51.2  | -0.1 |  130 |
snip...
| 2020-06-04 22:10:30 | hotWaterTemperature | 50.5  | -0.2 |    5 |
snip...
| 2020-06-05 11:30:45 | hotWaterTemperature | 46.1  | -0.1 |    25 |
+---------------------+---------------------+-------+------+-------+


</pre>Beispiel 2:


'''Für alle Auswertungsvarianten (außer sqlCmd) gilt:''' <br>
Summieren der Temperaturdifferenz auf Stundenbasis
Zusätzlich zu dem auszuwertenden Reading kann das Device mit angegeben werden um das Reporting nach diesen Kriterien einzuschränken. Sind keine Zeitgrenzen-Attribute angegeben, wird '1970-01-01 01:00:00' und das aktuelle Datum/Zeit als Zeitgrenze genutzt.


Verwendete DB-Variablen (@) :  device und reading, diff und delta werden für Berechnungen benötigt und sind bei erneutem Aufruf zurück zu setzen


<br>
Anmerkungen:


== Get ==
- Der Zeitraum wird in diesem Beispiel als die letzten 24 Stunden festgelegt


Die Get-Kommandos von DbRep dienen dazu eine Reihe von Metadaten der verwendeten Datenbankinstanz abzufragen. Dies sind zum Beispiel eingestellte Serverparameter, Servervariablen, Datenbankstatus- und Tabelleninformationen.
- Die Zeitdifferenz wird bei diesem Beispiel nicht ausgegeben
Die verfügbaren get-Funktionen sind von dem verwendeten Datenbanktyp abhängig. So ist für SQLite z.Zt. nur "svrinfo" verfügbar.
Die Funktionen liefern nativ sehr viele Ausgabewerte die über über funktionsspezifische Attribute abgrenzbar sind. Der Filter ist als kommaseparierte Liste anzuwenden. Dabei kann SQL-Wildcard (%) verwendet werden.


'''Hinweis:''' <br>
- Alle Differenzen nach der Vollen Stunde werden der nächsten Stunde zugeschlagen
Nach der Ausführung einer get-Funktion in der Detailsicht einen Browserrefresh durchführen um die Ergebnisse zu sehen !


- In FHEM wurde mit event-on-change-reading ins DBLog geschrieben


* '''dbstatus''' : listet globale Informationen zum MySQL Serverstatus (z.B. Informationen zum Cache, Threads, Bufferpools, etc. ). Es werden zunächst alle verfügbaren Informationen berichtet. Mit dem [[#Attribute | Attribut]] "showStatus" kann die Ergebnismenge eingeschränkt werden, um nur gewünschte Ergebnisse abzurufen. Detailinformationen zur Bedeutung der einzelnen Readings sind [http://dev.mysql.com/doc/refman/5.7/en/server-status-variables.html hier] verfügbar.
- Um 14:00 Uhr beginnt die Wärmepumpe das Warmwasser wieder aufzuheizen


::'''Bespiel:'''  
- Bei diesem Wärmespeicher sind Temperaturschwankungen im Bereich von Null Komma irgendwas, als Messwert recht ungenau, was im Beispiel 1 zu der Vielzahl an Messwerten geführt hat. Durch Beispiel 2 auf Stundenbasis lässt sich bereits mehr erkennen.  Hier wäre der Wärmepumpeneinsatz um 14:00 Uhr mit dem Pumpenvorlauf (-1.8) und der Lauf der Zirkulationspumpe um 8:00 und 8:30 Uhr zu sehen, was den Wärmespeicher um 1,2 Grad abgekühlt hat.<pre>
SET @device='Heizung';SET @reading='hotWaterTemperature';
SET @diff=0;SET @delta=NULL;


::get <name> dbstatus
SELECT xTIMESTAMP AS TIMESTAMP,READING,xVALUE AS VALUE,xDIFF AS DIFF
::attr <name> showStatus %uptime%,%qcache%   
  FROM
::Es werden nur Readings erzeugt die im Namen "uptime" und "qcache" enthalten
    (SELECT DATE_FORMAT(DATE_ADD(t1.TIMESTAMP,INTERVAL (IF(MINUTE(t1.TIMESTAMP) > 0, 60, 0)-MINUTE(t1.TIMESTAMP)) MINUTE),'%Y-%m-%d %H:00:00') AS xTIMESTAMP,
            t1.READING,
            round(t1.VALUE) AS xVALUE,
            sum(t1.DIFF)    AS xDIFF,
            sum(t1.DELTA)   AS xDELTA
      FROM
        (SELECT TIMESTAMP,READING,VALUE,
                if(@diff = 0,NULL, cast((VALUE-@diff) AS DECIMAL(3,1))) AS DIFF,
                @diff:=VALUE                                            AS curr_V,
                TIMESTAMPDIFF(MINUTE,@delta,TIMESTAMP)                  AS DELTA,
                @delta:=TIMESTAMP                                      AS curr_T
            FROM  history
            WHERE DEVICE    = @device  AND
                  READING    = @reading AND
                  TIMESTAMP >= NOW() - INTERVAL 1 DAY
            ORDER BY TIMESTAMP
        ) t1
      GROUP BY xTIMESTAMP WITH ROLLUP) x1


  WHERE xDELTA    IS NOT NULL AND
        xTIMESTAMP IS NOT NULL;
+---------------------+---------------------+-------+------+
| TIMESTAMP          | READING            | VALUE | DIFF |
+---------------------+---------------------+-------+------+
| 2020-06-04 12:00:00 | hotWaterTemperature |    46 | -0.1 |
| 2020-06-04 13:00:00 | hotWaterTemperature |    46 | -0.4 |
| 2020-06-04 14:00:00 | hotWaterTemperature |    44 | -1.8 |
| 2020-06-04 15:00:00 | hotWaterTemperature |    51 |  7.4 |
| 2020-06-04 18:00:00 | hotWaterTemperature |    51 | -0.1 |
snip...
| 2020-06-05 07:00:00 | hotWaterTemperature |    48 | -0.3 |
| 2020-06-05 08:00:00 | hotWaterTemperature |    48 | -0.2 |
| 2020-06-05 09:00:00 | hotWaterTemperature |    48 | -1.2 |
| 2020-06-05 10:00:00 | hotWaterTemperature |    47 | -0.3 |
snip...
+---------------------+---------------------+-------+------+
</pre>


* '''dbvars''' :  zeigt die globalen Werte der MySQL Systemvariablen. Enthalten sind zum Beispiel Angaben zum InnoDB-Home, dem Datafile-Pfad, Memory- und Cache-Parameter, usw. Die Ausgabe listet zunächst alle verfügbaren Informationen auf. Mit dem [[#Attribute | Attribut]] "showVariables" kann die Ergebnismenge eingeschränkt werden um nur gewünschte Ergebnisse abzurufen. Weitere Informationen zur Bedeutung der ausgegebenen Variablen sind [http://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html hier] verfügbar.
=== Alle aktuellsten readings eines Device im letzten Tagesverlauf (MySQL) ===
Beispiel :


::'''Bespiel:'''   
Verwendete DB-Variablen (@) : device
::get <name> dbvars
::attr <name> showVariables %version%,%query_cache%
::Es werden nur Readings erzeugt die im Namen "version" und "query_cache" enthalten


Anmerkungen:


* '''procinfo''' : listet die existierenden Datenbank-Prozesse in einer Tabelle auf (nur MySQL).
- Der Zeitraum wird in diesem Beispiel als die letzten 24 Stunden festgelegt
Typischerweise werden nur die Prozesse des Verbindungsusers (angegeben in DbLog-Konfiguration) ausgegeben. Sollen alle Prozesse angezeigt werden, ist dem User das globale Recht "PROCESS" einzuräumen.
Für bestimmte SQL-Statements wird seit MariaDB 5.3 ein Fortschrittsreporting (Spalte "PROGRESS") ausgegeben. Zum Beispiel kann der Abarbeitungsgrad bei der Indexerstellung verfolgt werden.
Weitere Informationen sind [https://mariadb.com/kb/en/mariadb/show-processlist/ hier] verfügbar.


- In FHEM wurde mit event-on-change-reading ins DBLog geschrieben, wodurch nicht alle readings immer zeitlich in der DB aufeinander folgen oder einige im Zeitraum mehrfach geschrieben wurden. Mit diesem Aufruf erscheinen immer die letzten Aktualisierungen.


* '''svrinfo''' : allgemeine Datenbankserver-Informationen wie z.B. die DBMS-Version, Serveradresse und Port usw. Die Menge der Listenelemente ist vom Datenbanktyp abhängig. Mit dem [[#Attribute | Attribut]] "showSvrInfo" kann die Ergebnismenge eingeschränkt werden. Weitere Erläuterungen zu den gelieferten Informationen sind [https://msdn.microsoft.com/en-us/library/ms711681(v=vs.85).aspx hier] zu finden.
- Sollten einige readings nicht erscheinen, so wurden diese vor dem Zeitraum letztmalig aktualisiert. Mit "INTERVAL [n] DAY könnten diese eventuell auch noch gefunden werden. Bitte hier die Laufzeit beachten!<pre>
 
SET @device = 'Heizung';
::'''Bespiel:'''
::get <name> svrinfo
::attr <name> showSvrInfo %SQL_CATALOG_TERM%,%NAME%
::Es werden nur Readings erzeugt die im Namen "SQL_CATALOG_TERM" und "NAME" enthalten
 
 
* '''tableinfo''' : ruft Detailinformationen der in einem MySQL-Schema angelegten Tabellen ab. Die ausgewerteten Schemata sind abhängig von den Rechten des verwendeten Datenbankusers (default: das DB-Schema der current/history-Tabelle). Mit dem [[#Attribute | Attribut]] "showTableInfo" können die Ergebnisse eingeschränkt werden. Erläuterungen zu den erzeugten Readings sind [http://dev.mysql.com/doc/refman/5.7/en/show-table-status.html hier] zu finden.
 
::'''Bespiel:'''
::get <name> tableinfo
::attr <name> showTableInfo current,history
::Es werden nur Information der Tabellen current,history angezeigt


SELECT t1.TIMESTAMP,t1.DEVICE,t1.READING,t1.VALUE
  FROM history t1
  INNER JOIN
  (select max(TIMESTAMP) AS TIMESTAMP,DEVICE,READING
      from history
      where DEVICE    = @device and
            TIMESTAMP > NOW() - INTERVAL 1 DAY
      group by READING) x
  ON x.TIMESTAMP = t1.TIMESTAMP AND
    x.DEVICE    = t1.DEVICE    AND
    x.READING  = t1.READING;
</pre>


<br>
<br>
== Attribute ==
=== Device / Reading Daten in eine CSV-Datei exportieren (MySQL) ===


Über die modulspezifischen Attribute wird die Abgrenzung der Auswertung und die Aggregation der Werte gesteuert.
Grundsätzlich gibt es für diesen Zweck das eingebaute Set-Kommando '''exportToFile'''.


'''Hinweis zur SQL-Wildcard Verwendung:''' <br>
Eine weitere Möglichkeit ist die Verwendung von spezifischen Datenbankkommandos, die mit Set '''sqlCmd''' ausgeführt werden können.
Innerhalb der Attribut-Werte für "device" und "reading" können SQL-Wildcard "%" angegeben werden. Das Zeichen "_" wird als SQL-Wildcard nicht unterstützt.
Dabei ist "%" = beliebig viele Zeichen. <br>
Dies gilt für alle Funktionen '''außer''' "insert", "importFromFile" und "deviceRename". <br>
Die Funktion "insert" erlaubt nicht dass die genannten Attribute das Wildcard "%" enthalten. Character "_" wird als normales Zeichen gewertet. <br>
In Ergebnis-Readings wird das Wildcardzeichen "%" durch "/" ersetzt um die Regeln für erlaubte Zeichen in Readings einzuhalten.
<br><br>


* '''aggregation'''    : Zusammenfassung der Device/Reading-Selektionen in Stunden,Tages,Kalenderwochen,Kalendermonaten oder "no". Liefert z.B. die Anzahl der DB-Einträge am Tag (countEntries), Summation von Differenzwerten eines Readings (sumValue), usw. Mit Aggregation "no" (default) erfolgt keine Zusammenfassung in einem Zeitraum sondern die Ausgabe ergibt alle Werte eines Device/Readings zwischen den definierten Zeiträumen.
Im Beispiel wird ein CSV File geschrieben, welches die Daten des Devices 'SMA_Energymeter' enthält.
Wichtig ist, nicht zur Trennung von SQL-Befehlen dienende Semikolons durch Verdopplung zu escapen (z.B. im String "...TERMINATED BY ';;'...").


<pre>
SET @TS = DATE_FORMAT(NOW(),'_%Y_%m_%d');


* '''allowDeletion'''   : schaltet die Löschfunktion des Moduls frei
SET @FOLDER = '/volume1/ApplicationBackup/';
SET @PREFIX = 'export';
SET @EXT    = '.csv';


SET @CMD = CONCAT(" SELECT *
FROM `fhemtest`.`history`
WHERE `DEVICE`='SMA_Energymeter' AND TIMESTAMP > DATE_SUB(CURRENT_DATE(),INTERVAL 1 DAY)
INTO OUTFILE '",@FOLDER,@PREFIX,@TS,@EXT,"'
FIELDS ENCLOSED BY '\"'
TERMINATED BY ';;'
ESCAPED BY '\"'","
LINES TERMINATED BY '\r\n';;
");


* '''device'''          : Abgrenzung der DB-Selektionen auf ein bestimmtes Device
PREPARE statement FROM @CMD;


 
EXECUTE statement;
* '''diffAccept'''      : gilt für Funktion diffValue. diffAccept legt fest bis zu welchem Schwellenwert eine berechnete positive Werte-Differenz zwischen zwei unmittelbar aufeinander folgenden Datensätzen akzeptiert werden soll (Standard ist 20). Damit werden fehlerhafte DB-Einträge mit einem unverhältnismäßig hohen Differenzwert von der Berechnung ausgeschlossen und verfälschen nicht das Ergebnis. Sollten Schwellenwertüberschreitungen vorkommen, wird das Reading "diff-overrun_limit-<diffLimit>" erstellt. (<diffLimit> wird dabei durch den aktuellen Attributwert ersetzt) Es enthält eine Liste der relevanten Wertepaare. Mit verbose 3 werden diese Datensätze ebenfalls im Logfile protokolliert.
</pre>
 
::'''Beispiel einer Ausgabe im Logfile beim Überschreiten von diffAccept=10:'''
 
::DbRep Rep.STP5000.etotal -> data ignored while calc diffValue due to threshold overrun (diffAccept = 10): 2016-04-09 08:50:50 0.0340 -> 2016-04-09 12:42:01 13.3440
 
::Der erste Datensatz mit einem Wert von 0.0340 ist untypisch gering zum nächsten Wert 13.3440 und führt zu einem zu hohen Differenzwert.
::Es ist zu entscheiden ob der Datensatz gelöscht, ignoriert, oder das Attribut diffAccept angepasst werden sollte.
 
 
* '''disable'''          : deaktiviert das Modul
 
 
* '''dumpComment''' : User-Kommentar. Er wird im Kopf des durch den Befehl "dumpMyQL clientSide" erzeugten Dumpfiles eingetragen.
 
 
* '''dumpDirLocal''' : Zielverzeichnis für die Erstellung von Dumps mit "dumpMySQL clientSide". default: "{global}{modpath}/log/" auf dem FHEM-Server.
Ebenfalls werden in diesem Verzeichnis alte Backup-Files durch die interne Versionsverwaltung von "dumpMySQL" gesucht und gelöscht wenn die gefundene Anzahl den Attributwert "dumpFilesKeep" überschreitet. Das Attribut dient auch dazu ein lokal gemountetes Verzeichnis "dumpDirRemote" DbRep bekannt zu machen.
 
 
* '''dumpDirRemote''' : Zielverzeichnis für die Erstellung von Dumps mit "dumpMySQL serverSide". default: das Home-Dir des MySQL-Servers auf dem MySQL-Host
 
 
* '''dumpMemlimit''' - erlaubter Speicherverbrauch für SQL-Script zur Generierungszeit (default: 100000 Zeichen). Bitte den Parameter anpassen, falls es zu Speicherengpässen und damit verbundenen Performanceproblemen kommen sollte.
 
 
* '''dumpSpeed''' - Anzahl der abgerufenen Zeilen aus der Quelldatenbank (default: 10000) pro Select durch "dumpMySQL ClientSide". Dieser Parameter hat direkten Einfluß auf die Laufzeit und den Ressourcenverbrauch zur Laufzeit.
 
 
* '''dumpFilesKeep''' : Es wird die angegeben Anzahl Dumpfiles im Dumpdir gelassen (default: 3). Sind mehr (ältere) Dumpfiles vorhanden, werden diese gelöscht nachdem ein neuer Dump erfolgreich erstellt wurde. Das globale Attribut "archivesort" wird berücksichtigt.
 
 
* '''executeAfterDump''' : Es kann ein FHEM-Kommando angegeben werden welches nach dem Dump ausgeführt werden soll. Funktionen sind in {} einzuschließen.
 
::Beispiel:
 
::attr <DbRep-device> executeAfterDump set og_gz_westfenster off;
::attr <DbRep-device> executeAfterDump {adump ("<DbRep-device>")}
 
::"adump" ist eine in 99_myUtils definierte Funktion.
 
<syntaxhighlight lang="perl">
    sub adump {
        my ($name) = @_;
        my $hash = $defs{$name};
        # die eigene Funktion, z.B.
        Log3($name, 3, "DbRep $name -> Dump ist beendet");
   
        return;
    }
</syntaxhighlight>
 
 
* '''executeBeforeDump''' : Es kann ein FHEM-Kommando angegeben werden welches vor dem Dump ausgeführt werden soll. Funktionen sind in {} einzuschließen.
 
::Beispiel:
 
::attr <DbRep-device> executeBeforeDump set og_gz_westfenster on;
::attr <DbRep-device> executeBeforeDump {bdump ("<DbRep-device>")}
 
::"bdump" ist eine in 99_myUtils definierte Funktion.
 
<syntaxhighlight lang="perl">
    sub bdump {
        my ($name) = @_;
        my $hash = $defs{$name};
        # die eigene Funktion, z.B.
        Log3($name, 3, "DbRep $name -> Dump startet");
   
        return;
    }
</syntaxhighlight>
 
 
* '''expimpfile'''      : Pfad/Dateiname für Export/Import in/aus einem File.
 
 
* '''limit''' : begrenzt die Anzahl der resultierenden Datensätze im select-Statement von "fetchrows" (default 1000). Diese Limitierung soll eine Überlastung der Browsersession und ein blockieren von FHEMWEB verhindern. Bei Bedarf entsprechend ändern bzw. die Selektionskriterien (Zeitraum der Auswertung) anpassen.
 
 
* '''optimizeTablesBeforeDump''' - wenn "1", wird vor dem Datenbankdump eine Tabellenoptimierung ausgeführt (default: 0). Dadurch verlängert sich die Laufzeit des Dump.
 
::'''Hinweis'''
::Die Tabellenoptimierung führt zur Sperrung der Tabellen und damit zur Blockierung von FHEM falls DbLog nicht im asynchronen Modus (DbLog-Attribut "asyncMode") betrieben wird !
 
 
* '''reading'''          : Abgrenzung der DB-Selektionen auf ein bestimmtes Reading
 
 
* '''readingNameMap'''  : der Name des ausgewerteten Readings wird mit diesem String für die Anzeige überschrieben
 
 
* '''readingPreventFromDel'''  : Komma separierte Liste von Readings die vor einer neuen Operation nicht gelöscht werden sollen
 
 
* '''role'''            : die Rolle des DbRep-Device. Standard ist "Client". Die Rolle "Agent" ist im Abschnitt [[#DbRep_Agent_-_automatisches_.C3.84ndern_von_Device-Namen_in_Datenbanken_und_DbRep-Definitionen_nach_FHEM_.22rename.22 | DbRep-Agent]] beschrieben.
 
 
* '''showproctime'''    : wenn gesetzt, zeigt das Reading "sql_processing_time" die benötigte Abarbeitungszeit (in Sekunden) für die SQL-Ausführung der durchgeführten Funktion. Dabei wird nicht ein einzelnes SQl-Statement, sondern die Summe aller notwendigen SQL-Abfragen innerhalb der jeweiligen Funktion betrachtet.
 
 
* '''showStatus'''      : grenzt die Ergebnismenge des Befehls "get ... dbstatus" ein. Es können SQL-Wildcard (%) verwendet werden.
 
::'''Bespiel:'''   
::attr <device> showStatus %uptime%,%qcache% 
::Es werden nur Readings erzeugt die im Namen "uptime" und "qcache" enthalten
 
 
* '''showVariables'''    : grenzt die Ergebnismenge des Befehls "get ... dbvars" ein. Es können SQL-Wildcard (%) verwendet werden.
 
::'''Bespiel:'''   
::attr <device> showVariables %version%,%query_cache%
::Es werden nur Readings erzeugt die im Namen "version" und "query_cache" enthalten
 
 
* '''showSvrInfo'''      : grenzt die Ergebnismenge des Befehls "get ... svrinfo" ein. Es können SQL-Wildcard (%) verwendet werden.
 
::'''Bespiel:'''   
::attr <device> showSvrInfo %SQL_CATALOG_TERM%,%NAME%
::Es werden nur Readings erzeugt die im Namen "SQL_CATALOG_TERM" und "NAME" enthalten
 
 
* '''showTableInfo'''    : grenzt die Ergebnismenge des Befehls "get ... tableinfo" ein. Es können SQL-Wildcard (%) verwendet werden.
 
::'''Bespiel:'''   
::attr <device> showTableInfo current,history
::Es werden nur Information der Tabellen "current" und "history" angezeigt
 
 
* '''sqlResultFormat''' : legt die Formatierung des Ergebnisses des Kommandos "set ... sqlCmd" fest. Mögliche Optionen sind:
 
::* separated - die Ergebniszeilen werden als einzelne Readings fortlaufend generiert. (default)
 
::* mline - das Ergebnis wird als Mehrzeiler im Reading SqlResult dargestellt. Feldtrenner ist "|".
 
::* sline - das Ergebnis wird als Singleline im Reading SqlResult dargestellt. Feldtrenner ist "|", Satztrenner ist"]|[".
 
::* table - das Ergebnis wird als Tabelle im Reading SqlResult dargestellt.
 
::* json - erzeugt das Reading SqlResult als JSON-kodierten Hash. Jedes Hash-Element (Ergebnissatz) setzt sich aus der laufenden Nummer des Datensatzes (Key) und dessen Wert zusammen.
 
::Die Weiterverarbeitung des Ergebnisses kann z.B. mit der folgenden userExitFn in 99_myUtils.pm erfolgen:
 
<syntaxhighlight lang="perl">
                sub resfromjson {
                  my ($name,$reading,$value) = @_;
                  my $hash  = $defs{$name};
 
                  if ($reading eq "SqlResult") {
                    # nur Reading SqlResult enthält JSON-kodierte Daten
                    my $data = decode_json($value);
             
            foreach my $k (keys(%$data)) {
             
          # ab hier eigene Verarbeitung für jedes Hash-Element
              # z.B. Ausgabe jedes Element welches "Cam" enthält
              my $ke = $data->{$k};
              if($ke =~ m/Cam/i) {
                my ($res1,$res2) = split("\\|", $ke);
                        Log3($name, 1, "$name - extract element $k by userExitFn: ".$res1." ".$res2);
              }
                }
                  }
                return;
                }
</syntaxhighlight>   
       
 
* '''timestamp_begin'''  : der zeitliche Beginn für die Datenselektion (*)
 
 
* '''timestamp_end'''    : das zeitliche Ende für die Datenselektion. Wenn nicht gesetzt wird immer die aktuelle Datum/Zeit-Kombi für das Ende der Selektion eingesetzt. (*)
 
 
::(*) Das Format von Timestamp ist wie in DbLog "YYYY-MM-DD HH:MM:SS". Für die Attribute "timestamp_begin", "timestamp_end" kann ebenso eine der folgenden Eingaben verwendet werden:
 
::* current_year_begin    : belegt das timestamp-Attribut dynamisch mit "<aktuelles Jahr>-01-01 00:00:00"
::* current_year_end      : belegt das timestamp-Attribut dynamisch mit "<aktuelles Jahr>-12-31 23:59:59"
::* previous_year_begin    : belegt das timestamp-Attribut dynamisch mit "<voriges Jahr>-01-01 00:00:00"
::* previous_year_end      : belegt das timestamp-Attribut dynamisch mit "<voriges Jahr>-12-31 23:59:59"
::* current_month_begin    : "aktueller Monat erster Tag 00:00:00"
::* current_month_end      : "aktueller Monat letzter Tag 23:59:59"
::* previous_month_begin  : "Vormonat erster Tag 00:00:00"
::* previous_month_end    : "Vormonat letzter Tag 23:59:59"
::* current_week_begin    : "erster Tag der akt. Woche 00:00:00"
::* current_week_end      : "letzter Tag der akt. Woche 23:59:59"
::* previous_week_begin    : "erster Tag Vorwoche 00:00:00"
::* previous_week_end      : "letzter Tag Vorwoche 23:59:59"
::* current_day_begin      : "aktueller Tag 00:00:00"
::* current_day_end        : "aktueller Tag 23:59:59"
::* previous_day_begin    : "Vortag 00:00:00"
::* previous_day_end      : "Vortag 23:59:59"
::* current_hour_begin    : "aktuelle Stunde:00:00"
::* current_hour_end      : "aktuelle Stunde:59:59"
::* previous_hour_begin    : "vorherige Stunde:00:00"
::* previous_hour_end      : "vorherige Stunde:59:59" 
 
::Natürlich sollte man immer darauf achten dass timestamp_begin < timestamp_end ist.
 
::'''Hinweis'''
 
::Wird das Attribut "timeDiffToNow" gesetzt, werden die evtentuell gesetzten Attribute "timestamp_begin" bzw. "timestamp_end" gelöscht.
::Das Setzen von "timestamp_begin" bzw. "timestamp_end" bedingt die Löschung von Attribut "timeDiffToNow", wenn gesetzt.
 
 
* '''timeDiffToNow'''    : der Selektionsbeginn wird auf den Zeitpunkt "aktuelle Zeit - timeDiffToNow" gesetzt (in Sekunden). Dadurch werden immer die letzten <timeDiffToNow>-Sekunden berücksichtigt (z.b. 86400 wenn immer die letzten 24 Stunden in die Selektion eingehen sollen). Die Timestampermittlung erfolgt dynamisch zum Ausführungszeitpunkt.
 
 
* '''timeOlderThan'''    : das Selektionsende wird auf den Zeitpunkt "aktuelle Zeit - timeOlderThan" gesetzt (in Sekunden). Dadurch werden alle Datensätze bis zu dem Zeitpunkt "<aktuelle Zeit> - <timeOlderThan>" berücksichtigt (z.b. wenn auf 86400 gesetzt werden alle Datensätze die älter als ein Tag sind berücksichtigt). Die Timestampermittlung erfolgt dynamisch zum Ausführungszeitpunkt.
 
 
* '''timeout'''          : das Attribut setzt den Timeout-Wert für die Blocking-Call Routinen (Standard 86400) in Sekunden
 
 
* '''userExitFn'''  : stellt eine Schnittstelle zur Ausführung eigenen Usercodes zur Verfügung
 
::Um die Schnittstelle zu aktivieren, wird zunächst die aufzurufende Subroutine in 99_myUtls.pm nach folgendem Muster erstellt:
 
<syntaxhighlight lang="perl">
sub UserFunction {
my ($name,$reading,$value) = @_;
my $hash = $defs{$name};
...
# z.B. übergebene Daten loggen
Log3 $name, 1, "UserExitFn $name called - transfered parameter are Reading: $reading, Value: $value " ;
...
 
return;
}
</syntaxhighlight>
 
::Die Aktivierung der Schnittstelle erfolgt durch Setzen des Funktionsnames im Attribut.
::Optional kann ein Reading:Value Regex als Argument angegeben werden. Wird kein Regex angegeben, sind alle Wertekombinationen wahr (entspricht .*:.*).
 
::'''Beispiel:'''
::attr <device> userExitFn UserFunction .*:.*
::"UserFunction" ist die Subroutine in 99_myUtils.pm.
 
::Grundsätzlich arbeitet die Schnittstelle OHNE Eventgenerierung. Sofern das Attribut gesetzt ist, erfolgt Die Regexprüfung NACH der Erstellung eines Readings. Ist die Prüfung WAHR, wird die angegebene Funktion aufgerufen. Zur Weiterverarbeitung werden der aufgerufenenen Funktion die folgenden Variablen übergeben:
 
::* $name - der Name des DbRep-Devices
::* $reading - der Namen des erstellen Readings
::* $value - der Wert des Readings




'''Hinweis:'''  der verwendete Datenbank-User benötigt das '''FILE''' Recht.


<br>
<br>
=== Gewichtete Mittelwerte von Zeitreihen (MySQL) ===
Zeitreihen in FHEM sind Messwerte (z.B. Temperatur, Stromverbrauch, PV-Produktion ...), die i.A. nicht zu äquidistanten Zeitpunkten ermittelt und protokolliert werden, sondern nur bei Änderung der Messgröße. Für eine Zusammenfassung ("Aggregation") zum Zwecke statistischer Auswertung oder einfach zur Datenreduktion sollte daher bei einer Mittelwertbildung auf zeitlich gewichtete Mittelung zurückgegriffen werden. Ein interessierender Gesamtzeitraum wird dazu in einzelne Mittelungsintervalle (sogen. ''bins'') aufgeteilt, für die dann der gewichtete Mittelwert berechnet wird.


== Readings ==
Die (non-blocking) DbRep-Funktion 'set averageValue' macht, in Verbindung mit den Attributen 'aggregation' (Auswahl einer Aggregationsperiode) und 'averageCalcForm'='avgTimeWeightMean' genau das. Dabei hat man als ''bin''-Länge 'minute', 'hour', 'day', 'week', 'month' und 'year' zur Verfügung.


Abhängig von der ausgeführten DB-Operation werden die Ergebnisse in entsrechnden Readings dargestellt. Zu Beginn einer neuen Operation werden alle alten Readings einer vorangegangenen Operation gelöscht um den Verbleib unpassender bzw. ungültiger Readings zu vermeiden.
Der folgende ''SQL-Query 1'' kennt als ''bin''-Längen auch Viertelstunden ('qhour'), Vierteltage ('qday') und Quartal ('qyear'). Die Parameter @sortformat=["minute"|"qhour"|"hour"|"qday"|"day"|"week"|"month"|"qyear"|"year"] und @weighted=["yes"|"no"] müssen vor Ausführung des Codes mit dem Attribut sqlCmdVars gesetzt werden, z.B.  
<small>set <Your_DbRep_device> sqlCmdVars SET @sortformat="hour", @weighted="yes", @count=<n>;</small>


Zusätzlich werden folgende Readings erzeugt:
<div class="mw-customtoggle-query1 wikia-menu-button">'''''SQL-Query 1'' ein/ausblenden'''</div>
 
<div class="mw-collapsible mw-collapsed" id="mw-customcollapsible-query1><small>
* '''state'''                         : enthält den aktuellen Status der Auswertung. Wenn Warnungen auftraten (state = Warning) vergleiche Readings "diff-overrun_limit-<diffLimit>" und "not_enough_data_in_period"  
SELECT
 
  avgtim,
* '''errortext'''                  : Grund eines Fehlerstatus
  CASE @weighted
 
    WHEN "yes" THEN
* '''background_processing_time''' : die gesamte Prozesszeit die im Hintergrund/Blockingcall verbraucht wird
      CAST((SUM(val * weight)/SUM(weight)) AS DECIMAL(12,4))
 
    ELSE
* '''sql_processing_time'''       : der Anteil der Prozesszeit die für alle SQL-Statements der ausgeführten Operation verbraucht wird
      CAST(sum(val) / count(val) AS DECIMAL(12,4))
 
  END AS avrg
* '''diff-overrun_limit-<diffLimit>''' : enthält eine Liste der Wertepaare die eine durch das Attribut "diffAccept" festgelegte Differenz <diffLimit> (Standard: 20) überschreiten. Gilt für Funktion "diffValue".
FROM (
 
  SELECT 
* '''less_data_in_period'''       : enthält eine Liste der Zeitperioden in denen nur ein einziger Datensatz gefunden wurde. Die Differenzberechnung berücksichtigt den letzten Wert der Vorperiode. Gilt für Funktion "diffValue".
    tim,
 
    avgtim,
<br>
    CASE @sortformat
 
       WHEN "week" THEN
== DbRep Agent - automatisches Ändern von Device-Namen in Datenbanken und DbRep-Definitionen nach FHEM "rename" ==
        date_format(tim, "%Y-%u 00:00:00")
 
      ELSE
Mit dem Attribut "role" wird die Rolle des DbRep-Device festgelegt. Die Standardrolle ist "Client". Mit der Änderung der Rolle in "Agent" wird das Device veranlasst auf Umbenennungen von Geräten in der FHEM Installation zu reagieren.
        avgtim
 
    END AS grouptim,
Durch den DbRep-Agenten werden folgende Features aktiviert wenn ein Gerät in FHEM mit "rename" umbenannt wird:  
    CASE
 
      WHEN avgtim!=nextavgtim THEN
*  in der dem DbRep-Agenten zugeordneten Datenbank (Internal Database) wird nach Datensätzen mit dem alten Gerätenamen gesucht und dieser Gerätename in allen betroffenen Datensätzen in den neuen Namen geändert.
        to_seconds(nextavgtim)-to_seconds(tim)
 
      WHEN avgtim!=preavgtim THEN
* in dem DbRep-Agenten zugeordneten DbLog-Device wird in der Definition das alte durch das umbenannte Device ersetzt. Dadurch erfolgt ein weiteres Logging des umbenannten Device in der Datenbank.
        to_seconds(nexttim)-to_seconds(avgtim)
 
      ELSE
* in den existierenden DbRep-Definitionen vom Typ "Client" wird ein evtl. gesetztes Attribut "device = alter Devicename" in "device = neuer Devicename" geändert. Dadurch werden Auswertungsdefinitionen bei Geräteumbenennungen automatisch konsistent gehalten.
        to_seconds(nexttim)-to_seconds(tim)  
 
    END AS weight, 
Mit der Änderung in einen Agenten sind folgende Restriktionen verbunden, die mit dem Setzen des Attributes "role = Agent" eingeschaltet und geprüft werden:
    CASE
       WHEN avgtim!=preavgtim THEN
        CASE @weighted WHEN "yes"
          THEN
            CASE
              WHEN avgtim!=nextavgtim THEN
                (preval*(to_seconds(tim)-to_seconds(avgtim)) + val*(to_seconds(nextavgtim)-to_seconds(tim)))/
                  (to_seconds(nextavgtim)-to_seconds(avgtim))
              ELSE
                (preval*(to_seconds(tim)-to_seconds(avgtim)) + val*(to_seconds(nexttim)-to_seconds(tim)))/
                  (to_seconds(nexttim)-to_seconds(avgtim))
            END
          ELSE
            val
        END
      ELSE
        val
    END AS val
  FROM (
    SELECT
      tim,
      nexttim,
      val,
      preval,
      avgtim,
      LAG(avgtim) OVER (ORDER BY tim) AS preavgtim,
      LEAD(avgtim) OVER (ORDER BY tim) AS nextavgtim
    FROM (
      SELECT
        timestamp AS tim,
        LEAD(timestamp) OVER (ORDER BY timestamp) AS nexttim,
        value AS val,
        LAG(value) OVER (ORDER BY timestamp) AS preval,
        CASE @sortformat
          WHEN "minute" THEN
            date_format(timestamp, "%Y-%m-%d %H:%i:00")
          WHEN "qhour" THEN
            concat(date_format(timestamp, "%Y-%m-%d %H:"),LPAD(15*(date_format(timestamp,"%i") div 15),2,"0"),":00")
          WHEN "hour" THEN
            date_format(timestamp, "%Y-%m-%d %H:00:00")
          WHEN "qday" THEN
            concat(date_format(timestamp, "%Y-%m-%d "),LPAD(6*(date_format(timestamp, "%H") div 6),2,"0"),":00:00")
          WHEN "day" THEN
            date_format(timestamp, "%Y-%m-%d 00:00:00")
          WHEN "week" THEN
            date_format(timestamp, "%Y-%m-%d 00:00:00")  
          WHEN "month" THEN
            date_format(timestamp, "%Y-%m-01 00:00:00")
          WHEN "qyear" THEN
            concat(date_format(timestamp, "%Y-"),LPAD(3*(date_format(timestamp, "%m") div 3),2,"0"),"-01 00:00:00")
          ELSE
            date_format(timestamp, "%Y-01-01 00:00:00")
        END AS avgtim     
      FROM
        history WHERE TIMESTAMP BETWEEN §timestamp_begin§ AND §timestamp_end§ AND §device§ AND §reading§ ORDER BY timestamp
    ) select3
  ) select2
) select1 GROUP BY grouptim
</small></div>
Die Auswahl von ''device'', ''reading'' und Zeitraum erfolgt über die üblichen Attribute. Es darf nur ein ''device'' und ein ''reading'' gewählt werden. Für den Zeitraum btte nur ''timestamp_begin'' und ''timestamp_end'' nutzen.


*  es kann nur einen Agenten pro Datenbank in der FHEM-Installation geben. Ist mehr als eine Datenbank mit DbLog definiert, können ebenso viele DbRep-Agenten eingerichtet werden
Will man keine festen Zeitraster, kann man den Gesamtzeitraum auch einfach in eine Anzahl ''count'' von bins aufteilen. Dafür steht der Parameter @count im obigen sqlCmdVars statement (für Query 1 hat er keine Bedeutung).  


* mit der Umwandlung in einen Agenten wird nur noch das Set-Komando "renameDevice" verfügbar sein sowie nur ein eingeschränkter Satz von DbRep-spezifischen Attributen zugelassen. Wird ein DbRep-Device vom bisherigen Typ "Client" in einen Agenten geändert, werden evtl. gesetzte und nun nicht mehr zugelassene Attribute glöscht.
<div class="mw-customtoggle-query2 wikia-menu-button">'''''SQL-Query 2'' ein/ausblenden'''</div>
<div class="mw-collapsible mw-collapsed" id="mw-customcollapsible-query2><small>
SELECT
  tim,
  CASE
    WHEN @weighted="yes" THEN
      CAST(sum(val * (to_seconds(nexttim)-to_seconds(tim))) / sum((to_seconds(nexttim)-to_seconds(tim))) AS DECIMAL(12,4))
    ELSE CAST(sum(val) / count(val) AS DECIMAL(12,4))
  END AS avrg
FROM (
  SELECT
    timestamp as tim,
    value as val,
    LEAD(timestamp) over (order by timestamp) nexttim,
    truncate(@count * (to_seconds(timestamp)-to_seconds(§timestamp_begin§)) / (to_seconds(§timestamp_end§)-to_seconds(§timestamp_begin§)),0)/@count as avgtim
  FROM
    history WHERE TIMESTAMP BETWEEN §timestamp_begin§ AND §timestamp_end§ AND §device§ AND §reading§ 
) select1 group by avgtim
</small></div>


Die Aktivitäten wie Datenbankänderungen bzw. Änderungen an anderen DbRep-Definitionen werden im Logfile mit verbose=3 protokolliert. Damit die renameDevice-Funktion bei großen Datenbanken nicht in ein timeout läuft, sollte das Attribut "timeout" entsprechend dimensioniert werden. Wie alle Datenbankoperationen des Moduls wird auch das Autorename nonblocking ausgeführt.
Zur Ausführung der Queries den Code entweder ins Eingabefeld hinter set sqlCmd (bzw. sqlCmdBlocking) kopieren und dann mit ''set'' ausführen, oder per ''set <Your_DbRep_device> sqlCmd <kopierter Code>'' in der FHEM Kommandozeile ausführen.


'''Beispiel''' für die Definition eines DbRep-Device als Agent:
Getestet wurden diese MySQL Beispiele mit MariaDB 10.3
 
<pre>
define Rep.Agent DbRep LogDB
attr Rep.Agent devStateIcon connected:10px-kreis-gelb .*disconnect:10px-kreis-rot .*done:10px-kreis-gruen
attr Rep.Agent icon security
attr Rep.Agent role Agent
attr Rep.Agent room DbLog
attr Rep.Agent showproctime 1
attr Rep.Agent stateFormat { ReadingsVal("$name","state", undef) eq "running" ? "renaming" : ReadingsVal("$name","state", undef). " » ProcTime: ".ReadingsVal("$name","sql_processing_time", undef)." sec"}
attr Rep.Agent timeout 86400
</pre>
 
 
<br>


Siehe auch im Forum: https://forum.fhem.de/index.php/topic,53584.msg1254036.html#msg1254036


<br>
<br>


== Praxisbeispiele / Hinweise und Lösungsansätze für verschiedene Aufgaben ==
== Praxisbeispiele / Hinweise und Lösungsansätze für verschiedene Aufgaben ==
=== Definieren eines DbRep-Devices ===
=== Definieren eines DbRep-Devices ===
[[Bild:DbRep_initialized.PNG|right|thumb|400px|]]
[[Bild:DbRep_initialized.PNG|right|thumb|400px|]]


Zeile 671: Zeile 603:
</pre>
</pre>


Bei der Definition werden die Zugangsdaten aus der DbLog-Instanz gelesen und das DbRep-Device mit der Datenbank verbunden. Nach der Definition ist der Status "initialized". Nach ca. 5 Sekunden wechselt er nach "connected" sofern die Verbindung zur Datenbank erfolgreich war. Zu welcher Datanbank das DbRep-Device sich verbunden hat, zeigt das ''Internal'' DATABASE.
Bei der Definition werden die Zugangsdaten aus der DbLog-Instanz gelesen und das DbRep-Device mit der Datenbank verbunden. Nach der Definition ist der Status "initialized". Die Verbindung zur Datenbank wird mit der ersten abzuarbeitenden Aufgabe hergestellt. Das Verhalten kann mit dem '''Attribut fastStart''' beeinflusst werden.
Zu welcher Datanbank das DbRep-Device sich verbunden hat, zeigt das ''Internal'' DATABASE.


Damit ist das DbRep-Device grundsätzlich einsatzbereit, aber noch nicht praxistauglich. Werden keine weiteren Eingrenzungen angegeben, kann mit dem so definierten Device mit dem Befehl
Damit ist das DbRep-Device grundsätzlich einsatzbereit, aber noch nicht praxistauglich. Werden keine weiteren Eingrenzungen angegeben, kann mit dem so definierten Device mit dem Befehl
Zeile 741: Zeile 674:
Sollte man nicht durch die Attribute "event-on-update-reading" bzw. "event-on-change-reading" die Eventerzeugung auf nur relevante Readings begrenzt haben, können in diesem Fall ebenso viele Events enstehen die das System entsprechend belasten können. <br>
Sollte man nicht durch die Attribute "event-on-update-reading" bzw. "event-on-change-reading" die Eventerzeugung auf nur relevante Readings begrenzt haben, können in diesem Fall ebenso viele Events enstehen die das System entsprechend belasten können. <br>
Deswegen wird empfohlen die '''Eventerzeugung sofort''' auf z.B. "state" zu '''begrenzen'''.
Deswegen wird empfohlen die '''Eventerzeugung sofort''' auf z.B. "state" zu '''begrenzen'''.
<br>
=== Ermittlung des monatlichen und durchschnittlichen Gasverbrauches mit Vaillant und eBusd MQTT ===
Es soll der monatliche Gasverbrauch in m3, kWh und Euro sowie der Durchnschnitt in Euro über die gewählten Monate ermittelt werden.
Die auswertenden Daten liegen als "Tics" vor. Es ist eine stetig steigende Zahl ohne Einheit. Sie wird als Reading aus einem Vaillant eBus-MQTT Device  geloggt. eBusd liefert PrEnergySumHc2 und PrEnergySumHwc1.
Beide Werte werden als Summe im Reading '''1_Brenner_Energy_Summe''' zusammengeführt und geloggt.
[[Bild:Screenshot_2022-12-25_143631.png|right|thumb|400px|]]
Für die Bereitstellung diverser Hilfswerte zur Berechnung existiert ein Dummy-Device '''VaillantControlDummy'''.
Dieses Device enthält in User-Attributen folgende Werte:
* '''Brennwert_kWh/m3''' -> den Brennwert in kWh pro m3 lt. Angaben des Lieferanten
* '''Zustandszahl'''    -> die Zustandszahl lt. Angaben des Lieferanten
* '''Multiplikator'''    -> ein Faktor zur Umrechnung der geloggten Tics (1_Brenner_Energy_Summe) in m3
* '''Tarif'''            -> der persönliche Tarif (Brutto) in €/kWh
Der Multiplikator wurde empirisch als Quotient aus den real verbrauchten m3 und den Tics über einem längeren Zeitraum ermittelt.
Das Ergebnis der Multiplikaktion von geloggten Tics und und Multiplikator ergibt die jeweilig verbrauchten m3. Dieser Wert
ist Toleranz/Fehler behaftet, hat sich aber in der Praxis als hinreichend genau zur Ermittlung des Gasverbrauchs erwiesen.
Das DbRep Device wird wie weiter oben beschrieben angelegt:
define Rep.gas.allmonths DbRep <DbLog-Devicename>
[[Bild:Screenshot_2022-12-25_143912.png|right|thumb|400px|]]
Die folgenden Attribute werden gesetzt um den auszuwertenden Zeitraum, sowie das auszuwertende Device und Reading in der Datenbank auszuwerten.
Die Werte für die Attribute sind natürlich dem persönlichen Umfeld anzupassen:
* '''device''' -> auszuwertendes Device (MQTT2_ebusd_bai)
* '''reading''' -> auszuwertendes Reading (1_Brenner_Energy_Summe)
* '''timestamp_begin''' -> Auswertungszeitraum Beginn (2022-10-01 00:00:00)
* '''timestamp_end''' -> Auswertungszeitraum Ende (current_year_end)
* '''aggregation''' -> month, d.h. der Auswertungszeitraum wird in Monatsscheiben aufgeteilt
* '''numDecimalPlaces''' -> die gwünschte Anzahl der Nachkommastellen im Ergebnis (0)
* '''diffAccept''' -> die akzeptierte Differenz von Diffenzwerten (1000000000), den Wert sehr hoch setzen um keine Datensätze von der Berechnung auszuschließen
* '''event-on-update-reading''' -> state,gas_.*
* '''eventMap''' -> /diffValue display:diffValuedisplay/ (für Verwendung in webCmd)
* '''webCmd''' -> diffValuedisplay
Wird das Device mit diesen Einstellungen gestartet, werden Readings der Form:
2022-10-31_07-05-03__MQTT2_ebusd_bai__1_Brenner_Energy_Summe__DIFF__2022-10
2022-11-30_08-38-18__MQTT2_ebusd_bai__1_Brenner_Energy_Summe__DIFF__2022-11
2022-12-25_12-29-47__MQTT2_ebusd_bai__1_Brenner_Energy_Summe__DIFF__2022-12
generiert. Sie liefern die Anzahl der Tics im jeweiligen Monat.
"Herzstück" zur Ermittlung der Zielwerte ist eine Perl Routine die im Attribut '''userExitFn''' hinterlegt wird:
<syntaxhighlight lang="perl">
{
  if ($READING =~ /1_Brenner_Energy_Summe__DIFF/ && $VALUE ne '-') {
    my $date      = (split '__', $READING)[0];
    my ($y,$m,$d) = $date =~ /^(\d{4})-(\d{2})-(\d{2})/x;
   
    my $cdd  = "VaillantControlDummy";                            # Steuerungsdummy Device
    my $mpk  = AttrVal($cdd,  'Multiplikator',    '0');
    my $zz    = AttrVal($cdd,  'Zustandszahl',    '0');          # Zustandszahl lt. Lieferant
    my $bw    = AttrVal($cdd,  'Brennwert_kWh/m3', '0');          # Brennwert kWh/m3 lt. Lieferant
    my $tarf  = AttrVal($cdd,  'Tarif',            '0');          # Kosten €/kWh   
    my $ndp  = AttrVal($NAME, 'numDecimalPlaces', '3');
    my $m3    = sprintf "%.${ndp}f", $VALUE/10000 * $mpk;          # verbrauchte m3
    my $kwh  = sprintf "%.${ndp}f", $m3 * $zz * $bw;              # Umrechnung m3 -> kWh
    my $cost  = sprintf "%.${ndp}f", $kwh * $tarf;                # Kosten = kWh * Tarif
    my $hash  = $defs{$NAME};
   
    readingsBulkUpdate ($hash, $date.'_gas_consumption_m3',        $m3);
    readingsBulkUpdate ($hash, $date.'_gas_consumption_kwh',      $kwh);
    readingsBulkUpdate ($hash, 'gas_cost_euro_'.$y.'-'.$m.'-'.$d, $cost);
  }
 
  if ($READING eq 'state' && $VALUE eq 'done') {
      my $n  = 0;
      my $v  = 0;
      my $avg = 0;
 
      for my $rdg ( grep { /gas_cost_euro_/x } keys %{$hash->{READINGS}} ) {
          $n++;
          $v += ReadingsNum ($name, $rdg, 0);
      }
 
      if ($n) {
          my $ndp3 = AttrVal($NAME, 'numDecimalPlaces', '3');
          $avg    = sprintf "%.${ndp3}f", $v / $n;
          readingsBulkUpdate ($hash, 'gas_cost_euro_average', $avg);
      }
  }
}
</syntaxhighlight>
Sobald ein Reading erstellt wird welches auf den Regex /1_Brenner_Energy_Summe__DIFF/ matcht, werden die User-Attribute aus dem Steuerungsdummy
''VaillantControlDummy'' abgerufen und damit die Berechnung der resultierenden m3 ($m3), kWh ($kwh) und Euro ($cost) durchgeführt.
Die User-Attribute können natürlich auch im DbRep Device selbst hinterlegt werden. Im vorliegenden Fall wird der Steuerungsdummy noch zu weiteren Aufgabe der Heizungssteuerung verwendet, sodass sich die Verwendung auch für diesen Use Case anbietet.
Es werden die Readings
* <Datum>_<Zeit>_gas_consumption_m3
* <Datum>_<Zeit>_gas_consumption_kwh
* gas_cost_euro_<Datum>
erzeugt.
Hat das Device den Report erfolgreich beendet, wird "state" mit dem Wert "done" erzeugt.
Dieser Wert wird ebenfalls in der Routine überwacht und dann der Durchschnittswert ($avg) aus den ermittelten Monatsergebnissen berechnet.
Mit dem Ergebnis wird das Reading:
* gas_cost_euro_average
erstellt.
Zur Gestaltung des Device Overwiew wird im Attribut '''stateFormat''' ebenfalls eine Perl Routine hinterlegt:
<syntaxhighlight lang="perl">
{
my $state = ReadingsVal($name, 'state',                    '');
my $gca  = ReadingsVal($name, 'gas_cost_euro_average', undef);
my $show  = '';
$show    .= defined $gca ? 'Durchschnittskosten Gas: '.$gca.' €' :
            $state;
$show    .= '<br>';
$show    .= $state =~ /connected/xs  ? FW_makeImage('10px-kreis-gelb')        :
            $state =~ /initialized/xs ? FW_makeImage('control_3dot_hor_s')      :
            $state =~ /disconnect/xs  ? FW_makeImage('10px-kreis-rot')          :
            $state =~ /done/xs        ? FW_makeImage('10px-kreis-gruen')        :
            $state =~ /Warning/xs    ? FW_makeImage('info_warning@darkorange') :
            $state =~ /running/xs    ? ''                                      :
            FW_makeImage('10px-kreis-rot');
"<div>".$show."</div>"
}
</syntaxhighlight>




Zeile 746: Zeile 826:


=== Datensätze (Devices) von einer Datenbank in eine andere umziehen (Export/Import)===
=== Datensätze (Devices) von einer Datenbank in eine andere umziehen (Export/Import)===
==== Zweck ====
==== Zweck ====


Zeile 774: Zeile 853:
</pre>
</pre>


kann man sich nun einen Überblick verschaffen wieviel Datensätze in der Quelldatenbank für "MelderCP1" enthalten sind und wieviel auch exportiert werden müssen. In dem Beispiel sind es, wie in dem Screenshot zu sehen, 1144 Datensätze.
kann man sich nun einen Überblick verschaffen, wie viele Datensätze in der Quelldatenbank für "MelderCP1" enthalten sind und wie viele auch exportiert werden müssen. In dem Beispiel sind es, wie in dem Screenshot zu sehen, 1144 Datensätze.


Falls noch nicht geschehen, wird in der bisherigen DbLog-Definition das zu loggende Device entfernt und in der neuen DbLog-Definition aufgenommen damit nun keine neuen Datensätze mehr für "MelderCP1" in die bisherige Datenbank geschrieben werden. Hierzu siehe die [http://fhem.de/commandref_DE.html#DbLog Commandref] zu DbLog.
Falls noch nicht geschehen, wird in der bisherigen DbLog-Definition das zu loggende Device entfernt und in der neuen DbLog-Definition aufgenommen, damit nun keine neuen Datensätze mehr für "MelderCP1" in die bisherige Datenbank geschrieben werden. Hierzu siehe die {{Link2CmdRef|Lang=de|Anker=DbLog}} zu DbLog.


==== Export ====
==== Export ====
Nun werden die Datensätze des Devices "MelderCP1" mit dem folgenden set-Kommando in die Datei "/media/sf_Backup_FHEM/meldercp1.txt" exportiert.
Nun werden die Datensätze des Devices "MelderCP1" mit dem folgenden set-Kommando in die Datei "/media/sf_Backup_FHEM/meldercp1.txt" exportiert.


Zeile 822: Zeile 900:


==== Import ====
==== Import ====
Im nächsten Schritt werden die exportierten Daten in die neue Datenbank importiert. Dazu wird in unserem DbRep-Device "Rep.MelderCP1" zunächst das "allowDeletion"-Attribut sicherheitshalber gelöscht.  
Im nächsten Schritt werden die exportierten Daten in die neue Datenbank importiert. Dazu wird in unserem DbRep-Device "Rep.MelderCP1" zunächst das "allowDeletion"-Attribut sicherheitshalber gelöscht.  
Zur Umstellung des DbRep-Devices auf die neue Datenbank wird die Definition von "Rep.MelderCP1" editiert und dort die neue DbLog-Instanz "LogDBShort" angegeben.
Zur Umstellung des DbRep-Devices auf die neue Datenbank wird die Definition von "Rep.MelderCP1" editiert und dort die neue DbLog-Instanz "LogDBShort" angegeben.
Zeile 831: Zeile 908:


Nach einem kurzen Moment wird der state von "Rep.MelderCP1" sich wieder von "initialized" auf "connected" ändern sofern die Umstellung funktioniert hat. Mit einem Vergleichslauf durch
Nach einem kurzen Moment wird der state von "Rep.MelderCP1" sich wieder von "initialized" auf "connected" ändern sofern die Umstellung funktioniert hat. Mit einem Vergleichslauf durch
[[Datei:Rep_MelderCP1_Vergleichslauf.PNG|right|thumb|300px|Anzahl Einträge von MelderCP1 in neuer Datenbank nach Umstellung]]


<pre>
<pre>
Zeile 838: Zeile 916:
wird ersichtlich, dass sich in dieser Datenbank noch keine (oder vllt. bereits neu geloggte) Einträge von "MelderCP1" befinden.
wird ersichtlich, dass sich in dieser Datenbank noch keine (oder vllt. bereits neu geloggte) Einträge von "MelderCP1" befinden.


[[Datei:Rep_MelderCP1_Vergleichslauf.PNG|right|thumb|300px|Anzahl Einträge von MelderCP1 in neuer Datenbank nach Umstellung]]


Nun kann der Import erfolgen. Mit  
Nun kann der Import erfolgen. Mit  
Zeile 847: Zeile 924:


werden die Daten aus der immer noch im Attribut "expimpFile" hinterlegten Datei "/media/sf_Backup_FHEM/meldercp1.txt" in die Datenbank "fhemshort" importiert. Dieser Vorgang wird als Transaktion ausgeführt. Es werden immer alle Daten oder, im Falle eines Fehlers, '''KEINE''' Daten importiert. Damit ist sichergestellt dass der Datenimport immer einen definierten Zustand hat. Sollte der Datenimport sehr umfangreiche Datensätze enthalten und somit schon absehbar sein dass der voreingestellte timeout-Wert von 86400 Sekunden nicht ausreichen wird, kann man vorsorglich das Attribut "timeout" höher setzen, z.B. auf das Doppelte).
werden die Daten aus der immer noch im Attribut "expimpFile" hinterlegten Datei "/media/sf_Backup_FHEM/meldercp1.txt" in die Datenbank "fhemshort" importiert. Dieser Vorgang wird als Transaktion ausgeführt. Es werden immer alle Daten oder, im Falle eines Fehlers, '''KEINE''' Daten importiert. Damit ist sichergestellt dass der Datenimport immer einen definierten Zustand hat. Sollte der Datenimport sehr umfangreiche Datensätze enthalten und somit schon absehbar sein dass der voreingestellte timeout-Wert von 86400 Sekunden nicht ausreichen wird, kann man vorsorglich das Attribut "timeout" höher setzen, z.B. auf das Doppelte).


Hier in dem Beispiel sind es lediglich 1144 Datensätze die sehr schnell importiert werden. Somit ist keine Anpassung des timeout-Parameters notwendig.
Hier in dem Beispiel sind es lediglich 1144 Datensätze die sehr schnell importiert werden. Somit ist keine Anpassung des timeout-Parameters notwendig.
Zeile 861: Zeile 934:
Nach dem erfolgreichen Import sollte das Attribut "expimpFile" wieder entfernt werden damit man nicht versehentlich einen erneuten Import durchführt.
Nach dem erfolgreichen Import sollte das Attribut "expimpFile" wieder entfernt werden damit man nicht versehentlich einen erneuten Import durchführt.
Das verwendete Rep.MelderCP1 kann nun wieder mit Zeitbegrenzungsattributen usw. versehen werden um die ursprüngliche Verwendung wieder herzustellen.
Das verwendete Rep.MelderCP1 kann nun wieder mit Zeitbegrenzungsattributen usw. versehen werden um die ursprüngliche Verwendung wieder herzustellen.
Die dargestellte Prozedur stellt einen Komplettumzug eines Devices dar, kann jedoch durch die Angabe der bekannten Attribute auch auf bestimmte Readings in der Datenbank oder Zeitgrenzen eingeschränkt werden. Dadurch würden nur bestimmte Datensätze exportiert und wieder importiert werden. Weiterhin sollte man daran denken die vorhandenen Auswertungsszenarien mit DbRep-Devices bzw. SVG-Diagrammen usw. auch auf die neue Datanbank umzustellen.
<br>
=== Inhalte einer primären Datenbank in eine andere Standby-Datenbank übertragen (syncStandby) ===
==== Zweck ====
Mit dem Export/Import Verfahren kann man, wie oben beschrieben, Daten zwischen verschiedenen Datenbanken austauschen. Mit dem Befehl "syncStandby" ist es aber mit relativ wenig Aufwand möglich, ein regelmäßig automatisch laufendes Übertragungsverfahren zwischen zwei Datenbanken zu etablieren.
Im vorliegenden Beispiel werden Datenbanken des gleichen Typs verwendet. Das Verfahren ist aber auch zwischen Datenbanken unterschiedlichen Typs anwendbar.
Anwendungsfälle dafür sind zum Beispiel:
* die Einrichtung eines Archivsystems
* die längerfristge Datenhaltung von Datensätzen eines bestimmten Devices/Readings in einer weiteren Datenbank um sie dort für umfangreiche Auswertungen zu nutzen (z.B. Daten einer PV-Anlage)
* Wechsel des Datenbanksystems, z.B. von SQLite zu MySQL/MariaDB
* säubern der Quelldaten von doppelten Datensätzen und nicht mehr benötigten Daten, Weiternutzung der aufgebauten Standbydatenbank als primäre Datenbank nach der Syncronisierung
Für das hier gezeigte Beispielszenario sollen Daten einer MariaDB-Quelldatenbank in einer andere MariaDB-Datenbank regelmäßig übertragen werden. Auf der Quelldatenbank werden die Daten aber nicht gelöscht, d.h. es entstehen zwei Datenbanken mit gleicher Datenbasis.
* Quelldatenbank ist eine MariaDB "fhemtest1" mit der DbLog-Instanz "LogDB1"
* Zieldatenbank ist eine MariaDB "fhemtest" mit der DbLog-Instanz "LogStby"
<br>
==== Die Quelldatenbank ====
Im vorliegenden Beispiel sind die Quelldatenbank und die Standby-Datenbank vom gleichen Typ MariaDB. Das Verfahren funktioniert ebenso zwischen Datenbanken unterschiedlichen Typs, also z.B. zur Übertragung von SQLite Daten in eine MariaDB.
Die Quelldatenbank ist im normalen Kontext die produktive FHEM-Logdatenbank. D.h. sie ist bereits eingerichtet und läuft mit einem entsprechenden DbLog-Device.
Die hier verwendete Quelldatenbank heißt "fhemtest1". Die Definition des dazu gehörenden DbLog-Devices "LogDB1" sei der Vollständigkeit halber hier erwähnt, wird aber natürlich bei jedem Nutzer individuell aussehen:
<pre>
define LogDB1 DbLog ./fhemtest1maria10.conf .*:(?!done).*
attr LogDB1 DbLogInclude CacheUsage
attr LogDB1 DbLogSelectionMode Exclude/Include
attr LogDB1 DbLogType History
attr LogDB1 addStateEvent 0
attr LogDB1 asyncMode 1
attr LogDB1 bulkInsert 1
attr LogDB1 cacheEvents 2
attr LogDB1 cacheLimit 2000
attr LogDB1 dbSchema fhemtest1
attr LogDB1 devStateIcon .*active:10px-kreis-gelb connected:10px-kreis-gruen .*disconnect:10px-kreis-rot
attr LogDB1 disable 0
attr LogDB1 room DbLog
attr LogDB1 showproctime 1
attr LogDB1 syncInterval 120
attr LogDB1 useCharfilter 1
attr LogDB1 verbose 2
</pre>
<br>
==== Die Standby Datenbank ====
Die Standby Datenbank ist das Synchronisationsziel.
Die Erstellung der Datenbank und die nachfolgende Definition des dazu gehörigen DbLog-Devices erfolgt weitgehend wie für eine "normale" Log-Datenbank wie in der Commandref beschrieben mit geringfügigen Anpassungen.
; 1. Anlegen der DB und Tabellen: wie in diesem [https://svn.fhem.de/trac/browser/trunk/fhem/contrib/dblog Link] hinterlegt, erfolgt die Erstellung mit den Befehlen  für MySQL:
::: CREATE DATABASE `fhemtest` DEFAULT CHARACTER SET utf8 COLLATE utf8_bin;
::: CREATE USER 'fhemuser'@'%' IDENTIFIED BY 'fhempassword';
::: CREATE TABLE `fhemtest`.`history` (TIMESTAMP TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, DEVICE varchar(64), TYPE varchar(64), EVENT varchar(512), READING varchar(64), VALUE varchar(128), UNIT varchar(32));
::: ALTER TABLE `fhemtest`.`history` ADD PRIMARY KEY(TIMESTAMP, DEVICE, READING);
::: CREATE TABLE `fhemtest`.`current` (TIMESTAMP TIMESTAMP, DEVICE varchar(64), TYPE varchar(64), EVENT varchar(512), READING varchar(64), VALUE varchar(128), UNIT varchar(32));
::: GRANT SELECT, INSERT, DELETE, UPDATE ON `fhemtest`.* TO 'fhemuser'@'%';
Es wird zusätzlich zu der normalen Erstellung der history-Tabelle ein primary Key für die Tabelle erstellt. Dieser Key verhindert dass Duplikate in der Tabelle erstellt werden und erleichtert dadurch sehr das Verfahren der Datenübertragung in der Folge.
[[Datei:stb1.PNG|right|thumb|300px|DbLog-Device für Standby-Datenbank]]
; 2. Erstellung der Konfigurationsdatei und Definition des DbLog-Devices für die Standby-Datenbank: Die Konfigurationsdatei (mariastby.conf) beinhaltet die Verbindunginformationen für das DbLog-Device und wird genau wie in der DbLog-Commandref beschrieben angelegt. Das DbLog-Device für die Standby-Datenbank wird nur für die nachfolgende Definition des benötigten DbRep-Devices gebraucht und wird so definiert, dass keinerlei Events geloggt werden. Das kann auf verschiedenen Wegen erreicht werden. Im Beispiel wird der Regex im DEF entsprechend aufgebaut.
::: define LogStby DbLog ./mariastby.conf aaaaaa:bbbbbb
::: attr LogStby DbLogType History
::: attr LogStby asyncMode 1
::: attr LogStby bulkInsert 1
::: attr LogStby cacheEvents 2
::: attr LogStby devStateIcon .*active:10px-kreis-gelb connected:10px-kreis-gruen .*disconnect:10px-kreis-rot
::: attr LogStby disable 0
::: attr LogStby room DbLog
::: attr LogStby showNotifyTime 1
::: attr LogStby showproctime 1
::: attr LogStby syncEvents 1
Die Erstellung der current-Tabelle ist für den vorgesehenen Zweck eigentlich nicht nötig, wird der Vollständigkeit halber mit dokumentiert.
Ist das DbLog-Device definiert und erfolgreich verbunden, ist dessen state "connected".
<br>
==== Definition des DbRep-Devices zur Synchronisation Quell- und Standby-Datenbank ====
Es existieren nun das DbLog Device "LogDB1" für die produktive Quelldatenbank und das DbLog Device "LogStby" für die Standby-Datenbank.
Für die Synchronisation wird ein DbRep-Device erstellt, welches mit dem DbLog-Device der '''produktiven Quelldatenbank''', also LogDB1, verbunden wird.
Wie üblich, wird im DEF der Name des entsprechenden DbLog-Devices angegeben, hier "LogDB1".
<pre>
defmod Rep.LogDB1.syncStandby DbRep LogDB1
attr Rep.LogDB1.syncStandby aggregation no
attr Rep.LogDB1.syncStandby event-on-update-reading state
attr Rep.LogDB1.syncStandby fastStart 1
attr Rep.LogDB1.syncStandby role Client
attr Rep.LogDB1.syncStandby room DbLog
attr Rep.LogDB1.syncStandby showproctime 1
attr Rep.LogDB1.syncStandby stateFormat { (ReadingsVal($name,"state", ""))." : SQL-Zeit: ".(ReadingsVal($name,"sql_processing_time", "")) }
attr Rep.LogDB1.syncStandby timeDiffToNow d:6
attr Rep.LogDB1.syncStandby verbose 3
</pre>
Mit den verschiedenen möglichen Zeitattributen ('''time.*''') und den Attributen '''device''' und '''reading''' wird abgegrenzt, welche Datensätze in die Standby-Datenbak übertragen werden sollen. In dem vorliegenden Beispiel werden mit jedem Synchronisationslauf alle in der DB vorhandenen Datensätze, die nicht älter als 6 Tage Tage sind, in die Standby-Datenbank übertragen.
'''Hinweis:''' <br>
Würde man einen solchen Lauf z.B. alle 4 Stunden durchführen, würden viele doppelte Datensätze in der  Datenbank entstehen. Der bei der Tabellenerstellung für die history-Tabelle angelegte primary Key verhindert das !
[[Datei:stb2.PNG|right|thumb|300px|syncStandby gestartet]]
Ein Synchronisationslauf wird nun gestartet mit:
<pre>
set <DbRep-Name> syncStandby <DbLog-Device Standby>
</pre>
In dem vorliegenden Beispiel ist das:
<pre>
set Rep.LogDB1.syncStandby syncStandby LogStby
</pre>
[[Datei:stb3.PNG|right|thumb|300px|syncStandby erfolgreich ausgeführt]]
Der Fortschritt der Datenübertragung sowie die evtl. eingefügten Datensätze (number of lines inserted) ist im Logfile mit verbose 3 ersichtlich:
<pre>
2020.01.24 17:12:41.186 3: DbRep Rep.LogDB1.syncStandby - total lines transfered to standby database: 6177
2020.01.24 17:12:46.411 3: DbRep Rep.LogDB1.syncStandby - total lines transfered to standby database: 23957
2020.01.24 17:12:51.710 3: DbRep Rep.LogDB1.syncStandby - total lines transfered to standby database: 24048
2020.01.24 17:12:57.733 3: DbRep Rep.LogDB1.syncStandby - total lines transfered to standby database: 24092
2020.01.24 17:13:03.505 3: DbRep Rep.LogDB1.syncStandby - total lines transfered to standby database: 24059
2020.01.24 17:13:08.891 3: DbRep Rep.LogDB1.syncStandby - total lines transfered to standby database: 23969
2020.01.24 17:13:13.420 3: DbRep Rep.LogDB1.syncStandby - total lines transfered to standby database: 17871
2020.01.24 17:13:13.421 3: DbRep Rep.LogDB1.syncStandby - number of lines inserted into "LogStby": 104
</pre>
[[Datei:stb4.PNG|right|thumb|300px|nur Daten der Devices SMA_Energymeter,MySTP_5000 werden übertragen]]
Nach dem erfolgreichen Sync-Lauf wird die benötigte Laufzeit und die Anzahl der übertragenen Datensätze in Readings angezeigt.
Mit dem Attribut '''aggragation''' wird gesteuert, in welchen '''Zeitscheiben''' die Selektion der Daten aus der Quelldatenbank und die Übertragung ausgeführt wird. Im Standard ohne gesetztes aggregation-Attribut ist es ein Tag. Stehen genügend Ressourcen zur Verfügung, kann dieser Wert auch auf "week" oder "month" gesetzt werden. Der Wert "hour" zur Verkleinerung des Datenpaketes ist ebenfalls möglich.
Bei jedem Lauf wird die Anzahl der eingefügten Datensätze im Reading '''number_lines_inserted_Standby''' angezeigt.
Die zu übertragenden Daten können natürlich sehr granular bestimmt werden. Sollen zum Beispiel nur die Daten der Devices SMA_Energymeter und MySTP_5000 übertragen werden, wird das Attribut '''device''' entsprechend gesetzt:
attr Rep.LogDB1.syncStandby SMA_Energymeter,MySTP_5000
Weiterhin bietet das Attribut '''reading''' eine Eingrenzung auf die gewünschten Readings und mit dem Attribut '''valueFilter''' kann eine Eingrenzung nur auf bestimmte vorkommende Werte vorgenommen werden.
Der Erfolg der Synchronisationsläufe kann sehr einfach mit einem separaten DbRep-Device (mit dem '''fetchrows''' Kommando) oder dem Tool phpMyAdmin (nebenstehend) überprüft werden.




<br>


==== regelmäßige Ausführung der Synchronisation ====


Ein einfaches AT kann dazu dienen den Synchronisationslauf mit dem Device '''Rep.LogDB1.syncStandby''' regelmäßig alle x-Stunden/Minuten auszuführen.


<pre>
define At.Sync.Stby at +*04:00:00 set Rep.LogDB1.syncStandby syncStandby LogStby
attr At.Sync.Stby room DbLog
</pre>


Die dargestellte Prozedur stellt einen Komplettumzug eines Devices dar, kann jedoch durch die Angabe der bekannten Attribute auch auf bestimmte Readings in der Datenbank oder Zeitgrenzen eingeschränkt werden. Dadurch würden nur bestimmte Datensätze exportiert und wieder importiert werden. Weiterhin sollte man daran denken die vorhandenen Auswertungsszenarien mit DbRep-Devices bzw. SVG-Diagrammen usw. auch auf die neue Datanbank umzustellen.




Zeile 935: Zeile 1.179:
Danach folgt im Reading die Angabe des Devices (STP_5000), des augewerteten Readings (etoday) und der Funktion die dieses Ergebnis ermittelt hat (MAX für maxValue).
Danach folgt im Reading die Angabe des Devices (STP_5000), des augewerteten Readings (etoday) und der Funktion die dieses Ergebnis ermittelt hat (MAX für maxValue).
Die letzte Angabe, hier "2016-10-07" definiert den Auswertungszeitraum. In dem Beispiel ist es der 07.10.2016. Würde die Berechnung mit aggregation=week durchgeführt werden, würde man als an dieser Stelle den Auswertungszeitraum "week_39" erhalten, also die Kalenderwoche 39.
Die letzte Angabe, hier "2016-10-07" definiert den Auswertungszeitraum. In dem Beispiel ist es der 07.10.2016. Würde die Berechnung mit aggregation=week durchgeführt werden, würde man als an dieser Stelle den Auswertungszeitraum "week_39" erhalten, also die Kalenderwoche 39.




Zeile 957: Zeile 1.197:


<br>
<br>
=== (regelmäßiges) löschen von Datenbanksätzen ===
 
=== (regelmäßiges) Löschen von Datenbanksätzen ===


Dieses DbRep-Device dient dazu einmalig oder verbunden mit einem AT-Device Einträge aus einer Datenbank zu löschen.
Dieses DbRep-Device dient dazu einmalig oder verbunden mit einem AT-Device Einträge aus einer Datenbank zu löschen.
Zeile 967: Zeile 1.208:


<pre>
<pre>
  attr Rep.Del.DbShort timeOlderThan 5552000
  attr Rep.Del.DbShort timeOlderThan d:180
  attr Rep.Del.DbShort allowDeletion 1
  attr Rep.Del.DbShort allowDeletion 1
</pre>
</pre>
Zeile 973: Zeile 1.214:
gesetzt. Die Zeitangabe erfolgt in Sekunden.
gesetzt. Die Zeitangabe erfolgt in Sekunden.


Insbesondere bei SQLite sollte während des Löschens kein paralleler Schreibprozess in Form des normalen Eventloggings stattfinden. MySQL/MariaDB kann es durchaus parallel verarbeiten. Um einen parallelen Zugriff durch DbLog-Loggging zu verhindern, kann das Logging vor der Löschung geschlossen und danach wieder geöffnet werden.
Dafür werden die Attribute mit z.B. diesen Werten gesetzt:
<pre>
attr Rep.Del.DbShort executeBeforeProc set LogDBShort reopen 7200
attr Rep.Del.DbShort executeAfterProc set LogDBShort reopen
</pre>
Dabei ist "LogDB" das mit dem DbRep-Device assoziierte DbLog-Device.
Der Löschvorgang wird mit
Der Löschvorgang wird mit


Zeile 981: Zeile 1.232:
gestartet. Nach Abschluß der Datenlöschung wird die Anzahl der deleted rows als Reading ausgegeben und auch im Log mit verbose=3 geschrieben.
gestartet. Nach Abschluß der Datenlöschung wird die Anzahl der deleted rows als Reading ausgegeben und auch im Log mit verbose=3 geschrieben.


Müssen sehr viele Datensätze gelöscht werden könnte nach 86400 Sekunden der Standardtimeout erreicht werden und der Vorgang mit "error" enden.
Müssen sehr viele Datensätze gelöscht werden, könnte nach 86400 Sekunden der Standardtimeout erreicht werden und der Vorgang mit "error" enden.
In diesem Fall ist der timout mit dem Attribut:
In diesem Fall ist der timout mit dem Attribut:


Zeile 1.018: Zeile 1.269:


Mit dieser Spezifikation werden alle Devices gelöscht die auf "5000" enden, also z.B. STP_5000 und MySTP_5000.
Mit dieser Spezifikation werden alle Devices gelöscht die auf "5000" enden, also z.B. STP_5000 und MySTP_5000.
(siehe auch Attribut "reading" in der {{Link2CmdRef|Anker=DbRepattr|Lang=de|Label=Commandref}}).


Ein einfaches AT kann dazu dienen den Löschjob über das Device Rep.Del.DbShort regelmäßig alle x-Stunden/Minuten auszuführen. Dadurch werden nicht mehr benötigte Datenbankeinträge automatisiert und nicht FHEM blockierend im Hintergrund entfernt.
Ein einfaches AT kann dazu dienen den Löschjob über das Device Rep.Del.DbShort regelmäßig alle x-Stunden/Minuten auszuführen. Dadurch werden nicht mehr benötigte Datenbankeinträge automatisiert und nicht FHEM blockierend im Hintergrund entfernt.
Zeile 1.026: Zeile 1.278:
</pre>
</pre>


==== erweiterte Anwendung von Selektionsbedingungen zur Löschung ====


<br>
Wie bei allen selektiven Funktionen im DbRep kann auch bei der Löschfunktion über die Attribute '''device''' und '''reading''' eine Beschränkung der zu löschenden Datensätze erfolgen. D.h. es werden nur Datensätze gelöscht, wenn die Selektionskriterien dieser Attribute erfüllt sind.
=== finden von alten Devicenamen in der DB und versenden/loggen einer Negativliste ===
 
Im Attribut '''device''' können einzelne Geräte, Geräte-Spezifikationen ({{Link2CmdRef|Anker=devspec|Lang=de|Label=devspec}}) sowie Geräte mit Wildcards angegeben werden.
Ist eine devspec angegeben, werden die Devicenamen vor der Selektion aus der Geräte-Spezifikationen und den aktuell in FHEM vorhandenen Devices aufgelöst sofern möglich.
Wird dem Device bzw. der Device-Liste oder Geräte-Spezifikation ein "EXCLUDE=" vorangestellt, werden diese Devices von der Selektion und damit von dem Löschvorgang ausgeschlossen. 
 
<pre>
attr <name> device TYPE=DbRep                    # es werden nur Datensätze der Geräte vom Modul-Typ "DbRep"
                                                  eingeschlossen
attr <name> device MySTP_5000                    # nur Datensätze des Gerätes "MySTP_5000" werden selektiert/gelöscht
attr <name> device SMA.*,MySTP.*                # Daten von Geräten die mit "SMA" oder "MySTP" beginnen, werden
                                                  selektiert/gelöscht
attr <name> device SMA_Energymeter,MySTP_5000    # nur Daten der beiden angegebenen Geräten werden selektiert/gelöscht
attr <name> device %5000                        # Daten von Geräten die mit "5000" enden werden in die Operation
                                                  eingeschlossen
attr <name> device TYPE=SSCam EXCLUDE=SDS1_SVS  # es werden Datensätze der Geräte vom Modul-Typ "SSCam" eingeschlossen,
                                                  aber das Gerät "SDS1_SVS" (auch vom Typ SSCam) wird ausgeschlosssen
attr <name> device EXCLUDE=SDS1_SVS              # Datensätze aller Geräte mit Ausnahme des Gerätes "SDS1_SVS" werden         
                                                  selektiert/gelöscht
attr <name> device EXCLUDE=TYPE=SSCam            # Datensätze aller Geräte mit Ausnahme der Geräte vom Typ "SSCam" werden         
                                                  selektiert/gelöscht
</pre>
 
 
Ähnlich erfolgt die Abgrenzung der DB-Selektionen auf ein bestimmtes oder mehrere Readings sowie exkludieren von Readings. Mehrere Readings werden als Komma separierte Liste angegeben. Es können SQL Wildcard (%) verwendet werden, aber '''keine Perl-Regex'''.
Wird dem Reading bzw. der Reading-Liste ein "EXCLUDE=" vorangestellt, werden diese Readings von der Selektion ausgeschlossen.
 
<pre>
attr <name> reading etotal                                    # Datensätze mit dem Reading "etotal" werden eingeschlossen
attr <name> reading et%                                        # Datensätze deren Reading mit "et" beginnt werden
                                                                eingeschlossen
attr <name> reading etotal,etoday                              # Datensätze mit den Readings "etotal" und "etoday" werden
                                                                in die Selektion eingeschlossen
attr <name> reading eto%,Einspeisung EXCLUDE=etoday            # Datensätze mit Readings beginnend mit "eto" sowie das
                                                                Reading "Einspeisung" werden selektiert, aber das Reading
                                                                "etoday" wiederum ausgeschlossen
attr <name> reading etotal,etoday,Ein% EXCLUDE=%Wirkleistung  # Datensätze mit Readings beginnend mit "Ein" sowie die
                                                                Readings "etotal" und "etoday" werden selektiert, wobei
                                                                Readings, die auf "Wirkleistung" enden, von der Löschung
                                                                ausgeschlossen werden
</pre>
 
 
Natürlich werden die Selektionsbedingungen durch eventuell gesetzte Zeiteingrenzungen mit den time*- Attributen ergänzt.
Die time*- Attribute sind mehrere im DbRep vorhandene Attribute, die alle mit "time" beginnen, z.B. timeOlderThan.
 
=== Auffinden von alten Devicenamen in der DB und versenden/loggen einer Negativliste ===


Wenn eine Datenbank längere Zeit gelaufen ist, Devices in FHEM hinzugefügt, geändert oder gelöscht wurden oder auch das DEF des DbLog-Devices angepasst wurde, befinden sich aller Wahrscheinlichkeit nach nicht mehr gewünschte/gebrauchte Datensätze von nicht mehr bestehenden Devices in der Datenbank.
Wenn eine Datenbank längere Zeit gelaufen ist, Devices in FHEM hinzugefügt, geändert oder gelöscht wurden oder auch das DEF des DbLog-Devices angepasst wurde, befinden sich aller Wahrscheinlichkeit nach nicht mehr gewünschte/gebrauchte Datensätze von nicht mehr bestehenden Devices in der Datenbank.
Zeile 1.063: Zeile 1.361:
</pre>
</pre>


"NaDevs" ist die Funktion in 99_myUtils.pm die aufgerufen werden soll. Die Angabe des Regex ".*:.*" ist optional. Nähere Informationen zur Funktionsweise sind in der [https://fhem.de/commandref_DE.html#DbRep Commandref] zu finden.
"NaDevs" ist die Funktion in 99_myUtils.pm die aufgerufen werden soll. Die Angabe des Regex ".*:.*" ist optional. Nähere Informationen zur Funktionsweise sind in der {{Link2CmdRef|Lang=de|Anker=DbRep}} zu finden.


Die "Positivliste" der in der Datenbank erlaubten Devices wird für das Beispiel in dem Attribut "comment" für den späteren Vergleich als String angegeben. Es ist natürlich auch z.B. eine Ableitung der im DEF-Regex des zugeordneten DbLog-Devices enthaltenen Device-Definitionen vorstellbar, sodass immer der aktuelle Stand dieser Definition als Grundlage für den Vergleich herangezogen wird.  
Die "Positivliste" der in der Datenbank erlaubten Devices wird für das Beispiel in dem Attribut "comment" für den späteren Vergleich als String angegeben. Es ist natürlich auch z.B. eine Ableitung der im DEF-Regex des zugeordneten DbLog-Devices enthaltenen Device-Definitionen vorstellbar, sodass immer der aktuelle Stand dieser Definition als Grundlage für den Vergleich herangezogen wird.  
Zeile 1.149: Zeile 1.447:


Nach Abschluss der Operation (signalisiert durch Reading "state = done") wird die erzeugte Liste im Logfile ausgegeben bzw. ein TelgramBot-Device versendet.
Nach Abschluss der Operation (signalisiert durch Reading "state = done") wird die erzeugte Liste im Logfile ausgegeben bzw. ein TelgramBot-Device versendet.
<br>


=== Größe der FHEM-Datenbank ermitteln ===
=== Größe der FHEM-Datenbank ermitteln ===
Zeile 1.199: Zeile 1.495:
enhalten.  
enhalten.  


Wird z.B. über ein AT dieser Befehl regelmäßig durch Rep.Fhem.Size ausgeführt und die Ergebnisse wiederum in DbLog gespeichert kann die Größenentwicklung der Datenbank mittels SVG-Diagrammen dargestellt werden.
Wird z.B. über ein AT dieser Befehl regelmäßig durch Rep.Fhem.Size ausgeführt und die Ergebnisse wiederum in DbLog gespeichert kann die Größenentwicklung der Datenbank mittels SVG-Diagrammen dargestellt werden.                                                                           [[Datei:tableinfo_SVG.PNG|left|thumb|300px|get Rep.Fhem.Size tableinfo]]
                                                                            [[Datei:tableinfo_SVG.PNG|left|thumb|300px|get Rep.Fhem.Size tableinfo]]


<br clear="all">
=== Readingwerte von DbRep in ein anderes Device übertragen ===


Um Readings aus DbRep in einen Dummy zu übertragen, können verschiedene Verfahren angewendet werden. Allen Verfahren ist gemeinsam, dass sie auf die Arbeitsweise der Funktionen von DbRep Rücksicht nehmen. Alle Funktionen von DbRep arbeiten, von Ausnahmen abgesehen, asynchron (non-blocking). Das hat den Vorteil, dass die Datenbankverarbeitungszeit bzw. eine Nichtverfügbarkeit der DB nicht hemmend auf FHEM wirkt, hat andererseits aber den Nachteil, dass die Ergebnisse einer Funktion nicht direkt nach dem Funktionsaufruf zur Verfügung stehen und somit nicht trivial innerhalb der FHEM-Hauptschleife weiterverarbeitet werden können.


<br>
==== Werte automatisch durch DbRep übertragen lassen (ab Version 8.40.0) ====


Ab DbRep Version 8.40.0 gibt es das Attribut '''autoForward''' mit dem eine integrierte Übertragung der DbRep-Ergebnissreadings in andere Devices (z.B. Dummy) eingerichtet werden kann. Diese Variante wird hier beschrieben.


Die weiteren manuellen Varianten werden in den nachfolgenden Abschnitten behandelt.


Um die integrierte Reading-Weiterleitung zu aktivieren wird des Attribut autoForward nach folgender Systematik gesetzt:
[[Datei:aforw1.PNG|right|thumb|300px|Übertragung aller Readings nach Dum.Rep.all]]
<pre>
{
  "<source-reading> => "<destination device> [=> <destination-reading>]",
  "<source-reading> => "<destination device> [=> <destination-reading>]",
  ...
}         
</pre>
[[Datei:aforw2.PNG|right|thumb|300px|Übertragung Readings mit "SUM" nach Dum.Rep.Sum]]
In der Spezifikation von '''<source-reading>''' können Wildcards (.*) verwendet werden um nur eine Auswahl der erzeugten Readings an das Zieldevice '''<destination device>''' weiterzuleiten. Ist der optionale Zusatz '''<destination-reading>''' angegeben, erfolgt die Speicherung der übertragenen Readings im Zieldevice mit dem angegebenen Namen. <br>
Der angegebene Readingname muss den Regularien zur Namensgebung von Readings genügen. Eventuell vorhandene nicht erlaubte Zeichen werden durch "_" ersetzt.


Fehlt der Zusatz <destination-reading>, werden die in DbRep erzeugten Readings 1:1 in das Zieldevice übertragen.


'''Beispiel:'''


<pre>
attr DbRepDev autoForward {
                            ".*"        => "Dum.Rep.All",   
                            ".*AVGAM.*" => "Dum.Rep    => average",
                            ".*SUM.*"  => "Dum.Rep.Sum => summary",
                          }
</pre>
Die Spezifikation erfüllt folgende Ziele:


# alle Readings werden zum Device "Dum.Rep.All" übertragen, der ursprüngliche Readingname bleibt im Ziel erhalten (Screenshot 1)
# Readings mit "AVGAM" im Namen werden zum Device "Dum.Rep" in das Reading "average" übertragen
# Readings mit "SUM" im Namen werden zum Device "Dum.Rep.Sum" in das Reading "summary" übertragen (Screenshot 2)






<br>
==== Werte mittels Event übetragen ====


Es sollen zum Beispiel die erzeugten Readings aus einem DbRep-Device in einen Dummy übetragen werden, die den Term "Grid" im Wortstamm tragen.


<br>
Ein Reading-Name der fetchrows-Funktion in DbRep ist immer nach folgendem Schema aufgebaut:
 
<Datum>_<Zeit>__<Unique-Index>__<Device>__<Reading>__<Dopplerindex>
z.B.: 2018-10-14_18-30-25__1__MyWetter__humidity
 
Datum und Zeit entsprechen dem Timestamp des gespeicherten Events in der Datenbank. Der Unique-Index identifiziert eventuell vorhandene Doubletten in der DB und der Dopplerindex ist ein Hilfsmittel, Datensätze die sich allein durch den Value-Wert unterscheiden, auch als unterschiedliche Readings erstellen zu können. Dieser Index wird nur bei Bedarf erstellt.


=== Reading von DbRep nach Dummy übertragen ===
Im folgenden Beispiel werden alle erzeugten Readings mit "Grid" im Namen des DbRep-Devices "Rep.SMAEM" in den Dummy übertragen und heißen dann genauso. Zunächst wird der Dummy angelegt.


In dem Beispiel werden alle erzeugten Readings des DbRep-Devices in den Dummy übertragen und heißen dann genauso.
<pre>
<pre>
define Dum.Rep dummy
define Dum.Rep dummy
attr Dum.Rep room DbLog
attr Dum.Rep room DbLog
</pre>


Mit dem nachfolgend angelegten Notify wird auf die z.B. von fetchrows erzeugten Events reagiert, der Event entsprechend gesplittet und verarbeitet in den Dummy übertragen.


<pre>
define N.Dum.Rep notify Rep.SMAEM:(\d).*Grid.* { fhem "setreading Dum.Rep ".(split(":",$EVTPART0))[0]." $EVTPART1"}
define N.Dum.Rep notify Rep.SMAEM:(\d).*Grid.* { fhem "setreading Dum.Rep ".(split(":",$EVTPART0))[0]." $EVTPART1"}
attr N.Dum.Rep room DbLog
attr N.Dum.Rep room DbLog
</pre>
</pre>


Soll im Dummy ein Reading mit eigenem Namen gefüllt werden, sieht das Notify so aus:
Soll im Dummy ein Reading mit eigenem Namen gefüllt werden, sieht das Notify z.B. so aus:


<pre>
<pre>
Zeile 1.237: Zeile 1.575:




==== Werte mittels Funktion DbReadingsVal übertragen ====
Sobald ein DbRep-Device definiert ist, wird die Funktion DbReadingsVal zur Verfügung gestellt. Mit dieser Funktion läßt sich, ähnlich dem allgemeinen ReadingsVal, der Wert eines Readings aus der Datenbank abrufen.  Die Befehlssyntax ist:
    DbReadingsVal("<name>","<device:reading>","<timestamp>","<default>")
Mit dieser Funktion kann ein Device/Reading-Wert aus der Datenbank gelesen und in einen Dummy gesetzt werden. "Name" ist dabei das abzufragende DbRep-Device.
Der Timestamp muss nicht genau bekannt sein. Es wird der zeitlich zu <timestamp> passendste Readingwert zurück geliefert, falls kein Wert exakt zu dem angegebenen Zeitpunkt geloggt wurde.
Um den aktuellsten in der Datenbank gespeicherten Wert eines Readings abzurufen, kann als Timestamp die aktuelle Zeit entsprechend formatiert verwendet werden.
Es soll z.B. der aktuellste Datenbankwert des Readings "etoday" vom Device "MySTP_5000" ausgelesen und im Dummy "Dum.Rep" als Reading "EnergyToday" gespeichert werden. Das beteilgte DbRep-Device heißt "Rep.Energy".
Für die Formatierung des aktuellen Timestamps kann die FHEM-Funktion FmtDateTime verwendet werden, welcher der aktuelle UNIX-Timestamp übergeben wird:
FmtDateTime(time)
Der Datenabruf aus der Datenbank sieht dann folgendermaßen aus:
DbReadingsVal("Rep.Energy","MySTP_5000:etoday",FmtDateTime(time),"")
Mit dem Befehl setreading kann der abgerufene Wert in dem Dummy-Device gespeichert werden:
{ fhem "setreading Dum.Rep EnergyToday ".DbReadingsVal("Rep.Energy","MySTP_5000:etoday",FmtDateTime(time),"") }
Damit der Datenabruf regelmäßig erfolgt und immer der aktuellste Wert von "etoday" in den Dummy eingtragen wird, kann dieses AT verwendet werden:
<pre>
define set.Dum.Rep_EnergyToday at +*00:01:00 { fhem "setreading Dum.Rep EnergyToday ".DbReadingsVal("Rep.Energy","MySTP_5000:etoday",FmtDateTime(time),"") }
</pre>
Somit erfogt eine Aktualisierung des Readings "EnergyToday" im Dummy "Dum.Rep" jede Minute.
Es ist zu beachten, dass die Funktionsausführung von DbReadingsVal blockierend erfolgt. Sollte die Datenbank nicht verfügbar sein oder lange Antwortzeiten besitzen, hat dies negative Auswirkungen auf FHEM, was sich in Blockierungszuständen äußert.
Möchte man solche eventuell möglichen Zustände vermeiden, kann die Befehlsausführung in die 99_myUtils unter Verwendung von [[Blocking_Call | Blocking Call]] ausgelagert werden.
Diese non-blocking Variante der Verwendung von DbReadingsVal wird nachfolgend skizziert.
In der 99_myUtils wird dazu der nachfolgende Code eingefügt, der drei kleine Subroutinen enthält:
<syntaxhighlight lang="perl">
############################################################################################################
#        DumRepEnergyToday (Reading mittels DbReadingsVal non-blocking setzen)   
############################################################################################################
sub DumRepEnergyToday ($$$$$) {
# initiale Routine zum Aufruf von BlockingCall unter Berücksichtigung von "RUNNING_SET_READING"
my ($name,$device,$reading,$destdev,$destread) = @_;
my $hash = $defs{$name};
return if($hash->{HELPER}{RUNNING_SET_READING});
$hash->{HELPER}{RUNNING_SET_READING} = BlockingCall("DumRepEnergyTodayNbl", "$name|$device|$reading|$destdev|$destread", "DumRepEnergyTodayNblDone");
return;
}
sub DumRepEnergyTodayNbl($) {
# die eigentliche Routine in der die potentiell blockierende Funktion ausgeführt wird
my ($string) = @_;
my ($name,$device,$reading,$destdev,$destread) = split("\\|", $string);
my $hash = $defs{$name};
my $val = DbReadingsVal($name,"$device:$reading",FmtDateTime(time),"");
$val = encode_base64($val,"");
return "$name|$val|$destdev|$destread";
}
sub DumRepEnergyTodayNblDone($) {
# Rückkherfunktion zur Ergebnisauswertung und Löschen von "RUNNING_SET_READING"
my ($string) = @_;
my @a    = split("\\|",$string);
my $hash = $defs{$a[0]};
my $name = $hash->{NAME};
my $val  = decode_base64($a[1]);
my $destdev  = $a[2];
my $destread = $a[3];
fhem "setreading $destdev $destread $val";
delete $hash->{HELPER}{RUNNING_SET_READING};
return;
}
</syntaxhighlight>
Der oben gezeigte Code stellt die einfachste Form der BlockingCall-Implementierung dar. Weitere Informationen dazu sind im weiter oben angegebenen Link zum BlockingCall-Wiki enthalten.
Um non-blocking Funktion aufzurufen, ist das bereits beschriebene AT-Device wie folgt zu ändern:
<pre>
define set.Dum.Rep_EnergyTodayNbl at +*00:01:00 { DumRepEnergyToday("Rep.Energy","MySTP_5000","etoday","Dum.Rep","EnergyToday") }
</pre>
Der aufgerufenen Funktion "DumRepEnergyToday" werden hierbei der Name des abzufragenden DbRep-Devices, der Name des auszuwertenden Devices, der Name des auszuwertenden Readings sowie das Ziel-Device und das Ziel-Reading als Argumente mitgegeben. Dieser Aufruf kann dadurch universell zum Abruf und Setzen verschiedener auszuwertender Device/Reading-Kombinationen verwendet werden.
=== Ein Userreading anlegen und für stateformat verwenden ===
Eine Besonderheit beim Modul DbRep besteht darin, dass sich die Namen der generierten Readings ändern können. Dadurch kann man für die Erstellung eines eigenen Userreading nicht einfach den Namen eines erstellten Readings, z.B. in der Funktion ReadingsVal, verwenden um dessen Wert zu ermitteln.
Eine Möglichkeit besteht darin eine Funktion in 99_myUtils anzulegen und mit dem Attribut "userExitFn" diese Funktion zu aktivieren/zu verwenden.
Für das Beispiel soll ein Reading "power_max" für den erreichten Maximalwert im Monat und der Timestamp des Maximalwertes aus einem Beispielreading "2018-06-01_13-23-02__STP_5000__total_pac__MAX__no_aggregation" (Name kann sich durch das jeweilige Datum ändern) extrahiert werden.
Zunächst wird die Funktion "calcpomax" in 99_myUtils angelegt.
<syntaxhighlight lang="perl">
############################################################################################################
########          power_max in DbRep erstellen 
############################################################################################################
sub calcpomax {
my ($name,$reading,$value) = @_;
my $hash = $defs{$name};
if($reading =~ /^.*total_pac__MAX.*$/) {
    $reading =~ /^(\d+)-(\d+)-(\d+)_(\d+)-(\d+)-(\d+)_.*$/;
    my $pmts = "$3.$2.$1 $4:$5:$6";
    my $fmtDateTime = FmtDateTime(gettimeofday());
    setReadingsVal($hash,"power_max",$value,$fmtDateTime);
    setReadingsVal($hash,"power_max_ts",$pmts,$fmtDateTime);
}
return;
}
</syntaxhighlight>
Wie in der Commandref zu {{Link2CmdRef|Anker=DbRep|Lang=de|Label=DbRep}} zu lesen ist, werden der im Attribut "userExitFn" hinterlegten Funktion jeweils der Name des aufrufenden Devices, das Reading und dessen Wert übergeben. Mit ein paar Zeilen Code wird der übergebene Readingsname auf das Vorhandensein des statischen Namensteils (total_pac__MAX) geprüft und dementsprechend der Readingname "power_max" mit dem Wert angelegt, wenn der Regex auf den Readingnamen matched.
Gleiches gilt für das Reading "power_max_ts", welches den Zeitpunkt des erreichten Maximalwertes enthalten soll.
Die Aktivierung der Schnittstelle erfolgt im DbRep-Device mit
<pre>
attr <name> userExitFn calcpomax .*:.*
</pre>
Mit jedem Lauf von
<pre>
set <name> maxvalue
</pre>
wird nun das neue Reading mit angelegt und kann z.B. in einem stateformat verwendet werden: 
[[Datei:Rep.powmax.PNG|right|thumb|500px|]]
<pre>
attr <name> stateformat
if(ReadingsVal($name,"power_max",0)) {
  (ReadingsVal($name,"power_max",0)*1000)." Watt (erreicht am ".ReadingsVal($name,"power_max_ts","").")";
} else {
  ReadingsVal($name,"state","done");
}
}
</pre>
Nachfolgend noch die Definition des verwendeten DbRep-Devices für diese Auswertung:
<pre>
defmod Rep.total_pac.currmonth DbRep LogDB
attr Rep.total_pac.currmonth aggregation no
attr Rep.total_pac.currmonth alias Peak WR-Leistung aktueller Monat
attr Rep.total_pac.currmonth allowDeletion 0
attr Rep.total_pac.currmonth devStateIcon connected:10px-kreis-gelb .*disconnect:10px-kreis-rot .*done:10px-kreis-gruen
attr Rep.total_pac.currmonth device STP_5000
attr Rep.total_pac.currmonth disable 0
attr Rep.total_pac.currmonth event-on-update-reading state
attr Rep.total_pac.currmonth group SMA Inverter
attr Rep.total_pac.currmonth reading total_pac
attr Rep.total_pac.currmonth room Energie
attr Rep.total_pac.currmonth showproctime 1
attr Rep.total_pac.currmonth stateFormat {  \
if(ReadingsVal($name,"power_max",0)) {\
  (ReadingsVal($name,"power_max",0)*1000)." Watt (erreicht am ".ReadingsVal($name,"power_max_ts","").")";;\
} else {\
  ReadingsVal($name,"state","done");;\
}\
}
attr Rep.total_pac.currmonth timestamp_begin current_month_begin
attr Rep.total_pac.currmonth timestamp_end current_month_end
attr Rep.total_pac.currmonth userExitFn calcpomax .*:.*
attr Rep.total_pac.currmonth verbose 2
</pre>
<br>


=== datenbankgestützte Erstellung der Energiebilanz einer SMA PV-Anlage mit Überschußeinspeisung ===
=== datenbankgestützte Erstellung der Energiebilanz einer SMA PV-Anlage mit Überschußeinspeisung ===
[[datenbankgestützte Erstellung der Energiebilanz einer SMA PV-Anlage mit Überschußeinspeisung | Hier]] geht's zum Beitrag.
<br>
=== DbRep-Kommandos regelmäßig mit einem at-Device ausführen ===
Sollen DbRep-Kommandos regelmäßig zu bestimmten Zeiten bzw. Intervallen ausgeführt werden, kann ein at-Device (alternativ z.B. DOIF) dafür definiert und verwendet werden.
Als Beispiel soll die regelmäßige Ausführung des SQL-Statements
select device, count(*) from history group by DEVICE
über das Set-Kommando '''sqlCmd''' dienen.
Zunächst wird das DbRep-Device definiert welches zur Ausführung des Kommandos dienen soll.
<pre>
define Rep.LogDB.sqlResult DbRep LogDB
attr Rep.LogDB.sqlResult devStateIcon initialized:control_3dot_hor_s connected:10px-kreis-gelb .*disconnect:10px-kreis-rot .*done:10px-kreis-gruen
attr Rep.LogDB.sqlResult event-on-update-reading state
attr Rep.LogDB.sqlResult fastStart 1
attr Rep.LogDB.sqlResult room Datenbank
attr Rep.LogDB.sqlResult showproctime 1
attr Rep.LogDB.sqlResult sqlResultFormat table
attr Rep.LogDB.sqlResult verbose 2
</pre>
Die Definition Syntax wurde bereits weiter oben erläutert. Das Attribut '''sqlResultFormat''' legt fest in welcher Art und Weise das Ergebnis präsentiert werden soll. Auch andere Formate wie JSON sind implementiert.
Um das oben angegbene SQL-Statement zum Beispiel alle 6 Stunden 20 Minuten auszuführen, dient die folgende at-Definition:
<pre>
define At.LogDB.sqlResult at +*06:20:00 set Rep.LogDB.sqlResult sqlCmd select device, count(*) from history group by DEVICE
attr At.LogDB.sqlResult icon clock
attr At.LogDB.sqlResult room Datenbank->Produktiv
</pre>
Weitere Informationen sind in der [https://fhem.de/commandref_DE.html#at at-CommandRef] beschrieben.
Natürlich sind alle verfügbaren DbRep Set/Get-Befehle über diesen Weg ausführbar.
Es wird empfohlen das definierte DbRep-Device nur für Auswertungsaufgaben zu verwenden die eine gleiche Attributierung des Devices verlangen.
Für weitere Aufgaben sollten weitere DbRep-Devices definiert und entsprechend eingestellt werden.
<br>
=== DbRep Chain - die Kommandokette ===
Für regelmäßig auszuführende Aufgaben erstellt man sich je ein separates DbRep-Device und setzt die Attribute wie es für die spezifische Aufgabe erforderlich ist.
Zu diesem Zweck kopiert man sich einfach ein vorhandenes DbRep-Device mit dem FHEM ''copy'' Kommando und passt es danach entsprechend an. Es können beliebig viele DbRep-Devices angelegt werden.
Häufig besteht der Wunsch, verschiedene Datenbankaktionen nacheinander ablaufen zu lassen, z.B. erst ''minValue writeToDB'' danach ''maxValue writeToDB'' sowie ''averageValue writeToDB'' nacheinander auszuführen.
Eine solche Verkettung kann durch die Nutzung des ''executeAfterProc'' Attributes erstellt werden.
Für das Beispiel werden 3 DbRep-Devices angelegt:
* Rep_Min
* Rep_Max
* Rep_Avg
Das erste Device (Rep_Min) soll für ''minValue writeToDB'' genutzt werden. In diesem Device wird das Attribut gesetzt:
attr Rep_Min executeAfterProc set Rep_Max maxValue writeToDB
Dieses Attribut zeigt auf das zweite Device (Rep_Max), welche für ''maxValue writeToDB'' verwendet werden soll.
In diesem Device wird wiederum das Attribut gesetzt:
attr Rep_Min executeAfterProc set Rep_Avg averageValue writeToDB


[[datenbankgestützte Erstellung der Energiebilanz einer SMA PV-Anlage mit Überschußeinspeisung | Hier]] geht's zum Beitrag.


Die gesamte Chain startet mit einem at-Device, notify oder dergleichen mit dem Kommando:
  set Rep_Min minValue writeToDB
'''Funktionsweise:''' <br>
Die Attribute executeBeforeProc und executeAfterProc führen jeweils das in ihnen hinterlegte FHEM-Kommando oder Perl-Funktion vor bzw. nach der Ausführung des gestarteten DbRep-Kommandos aus. <br>
In dem dargestellten Beispiel startet das at-Device mit
  set Rep_Min minValue writeToDB
die Funktion ''minValue writeToDB'' im Device ''Rep_Min''.  Wenn das DbRep ''Rep_Min'' mit seiner Aufgabe fertig ist, startet es automatisch über executeAfterProc das DbRep ''Rep_Max'' die Ausführung von '''maxValue writeToDB'''. Hat DbRep ''Rep_Max''seine Aufgabe beendet, startet es wiederum im letzten Device ''Rep_Avg'' das Kommando ''averageValue writeToDB''. <br>
Mit diesem Verfahren läuft immer nur ein Kommando der Reihe nach über die Datenbank.
Natürlich kann diese Chain beliebig erweitert werden, oder im ersten DbRep vor dem Start der Chain das angeschlossene DbLog-Device pausiert werden mit:
attr Rep_Min executeBeforeProc set <DbLog-Device> reopen 7200
Die Beendigung der Pause des DbLog kann das letzte DbRep ''Rep_Avg'' übernehmen durch Setzen des Attributes:
attr Rep_Avg executeAfterProc set <DbLog-Device> reopen
<br>
=== Abarbeitung einer sequentiellen Befehlskette mittels non-blocking sqlCmd-Kommandos ===
('''Hinweis:''' In der aktuellen DbRep-Version kann die seqientielle Kommandoabarbeitung über die [[#DbRep_Chain_-_die_Kommandokette|Chain-Funktionalität]] realisiert werden.)
Ein Vorteil von DbRep ist die nicht blockierende Arbeitsweise fast aller DbRep-Befehle (auf Ausnahmen wird in der Commandref hingewiesen).
Daraus ergibt sich aber auch das Merkmal, dass nach dem Funktionsaufruf nicht auf das Ergebnis gewartet wird und die FHEM-Schleife 
unmittelbar nach dem Start des Befehls weiterläuft. Um mehrere voneinander abhängige Befehle, die eine festgelegte Reihenfolge erfüllen müssen, abzuarbeiten, kann die nachfolgend beschriebene Technik zur Kettenbildung angewendet werden. Diese Technik wird anhand einer einfachen Demo erläutert.
Das konstruierte Ziel der Kette ist folgendes:
* Es soll nacheinander der Insert eines Datensatzes durchgeführt, danach ein Select dieses Datensatzes, ein Update auf ein Feld und zuletzt ein delete dieses Datensatzes in dieser Reihenfolge ausgeführt werden
Demnach sich insgesamt vier SQL-Befehle nacheinander auszuführen (Insert, Select, Update, Delete).
Zunächst werden vier identische DbRep-Devices angelegt, die sich lediglich durch ihren Namen unterscheiden. Das wären die Devices:
<pre>
Rep.insert, Rep.select, Rep.update, Rep.delete
</pre>
Zur Anlage kann ein DbRep-Device als Vorlage erstellt und nachfolgend dreimal kopiert werden.
<pre>
define Rep.insert DbRep LogDB
attr Rep.insert allowDeletion 1
attr Rep.insert showproctime 1
attr Rep.insert userExitFn chain
copy Rep.insert Rep.select
copy Rep.insert Rep.update
copy Rep.insert Rep.delete
</pre>
Das wichtige Detail dieser Devices ist das gesetzte Attribut "userExitFn = chain". Dieses Attribut aktiviert eine Schnittstelle zum Aufruf von benutzereigenem Code nach dem Abschluß eines Datenbank-Calls (siehe Commandref).
Der benutzereigene Code wird nach folgendem Schema in die 99_myUtils.pm eingebaut:
<syntaxhighlight lang="perl">
#############################################################################################
#  sqlCmd Commandchain zur sequentiellen Abarbeitung von non-blocking DbRep-Befehlen
#
#  genutzte werden vier DbRep-Devices:  Rep.insert, Rep.select, Rep.update, Rep.delete
#     
#############################################################################################
sub chain {
my ($name,$reading,$value) = @_;
my $hash  = $defs{$name};
my $device = "Testdevice";
my $model  = "Testmodel";
my $rc;
if ($name eq "Rep.insert" && $reading eq "chainstart") {
    # Start der 1. Operation (insert)
    CommandSet(undef,"Rep.insert sqlCmd insert into history (DEVICE, TYPE, EVENT, READING, VALUE, UNIT)
  values (\"$device\", \"$model\", \"definition\", \"DEF\", \"Hugo\", \"Emma\")");
    Log3 $name, 1, "$name - Start chain with insert";
    return; 
}
if($reading eq "state" && $value eq "done") {
    if ($name eq "Rep.insert") {
        # Auswertung der 1. Operation (insert)
        $rc = ReadingsVal($name, "SqlResultRow_1", "");
        Log3 $name, 1, "$name - number of inserts: $rc";
       
        # Start der 2. Operation (select)
        CommandSet(undef,"Rep.select sqlCmd select VALUE from history where device=\"$device\" and READING=\"DEF\" LIMIT 1;");
    }
 
    if ($name eq "Rep.select") {
        # Auswertung der 2. Operation (select)
        $rc = ReadingsVal($name, "SqlResultRow_1", "");
        Log3 $name, 1, "$name - return of select: $rc";
       
        # Start der 3. Operation (update)
        CommandSet(undef,"Rep.update sqlCmd update history set UNIT=\"neu\" where DEVICE=\"$device\";");
    }
   
    if ($name eq "Rep.update") {
        # Auswertung der 3. Operation (update)
        $rc = ReadingsVal($name, "SqlResultRow_1", "");
        Log3 $name, 1, "$name - number of updates: $rc";
       
        # Start der 4. Operation (delete)
        CommandSet(undef,"Rep.delete sqlCmd delete from history where device = \"$device\" and UNIT = \"neu\";");
    }
   
    if ($name eq "Rep.delete") {
        # Auswertung der 4. Operation (delete)
        $rc = ReadingsVal($name, "SqlResultRow_1", "");
        Log3 $name, 1, "$name - number of deletes: $rc";
    }
}
return;
}
</syntaxhighlight>
Die gesamte Abarbeitung wird gestartet mit dem Aufruf:
<pre>
chain("Rep.insert", "chainstart")
</pre>
bzw. in der Kommandozeile im FHEMWEB mit:
<pre>
{chain("Rep.insert", "chainstart")}
</pre>
Nach dem initialen Aufruf des Insert wird die sub "chain" beendet und nach dem insert-Befehl durch das Device "Rep.insert" wieder aufgerufen. Dabei wird diese sub nach jedem erstellten Reading aufgerufen und dabei neben dem eigenen Devicenamen auch der Readingname sowie dessen Wert zur Auswertung übergeben.
Durch die if-Zweige wird bei jedem Durchlauf der richtige Entrypoint für den nachfolgenden Funktionsaufruf ermittelt und ausgeführt.
Nach der Abarbeitung aller vorgesehenen SQL-Statements ist die erfolgreiche Chainverkettung anhand der Ergebnisse im Logfile ersichtlich:
<pre>
2018.10.24 23:57:37.602 1: Rep.insert - Start chain with insert
2018.10.24 23:57:37.815 1: Rep.insert - number of inserts: 1
2018.10.24 23:57:37.901 1: Rep.select - return of select: Hugo
2018.10.24 23:57:38.033 1: Rep.update - number of updates: 1
2018.10.24 23:57:38.176 1: Rep.delete - number of deletes: 1
</pre>


=== Summe aller Einschaltzeiten eines Gerätes ===
=== Summe aller Einschaltzeiten eines Gerätes ===


[[Summe aller Einschaltzeiten eines Gerätes | Hier]] geht's zum Beitrag.
[[Summe aller Einschaltzeiten eines Gerätes | Hier]] geht's zum Beitrag.
=== Eine Device spezifische Aufbewahrungszeit (Retention Time) in der Datenbank realisieren ===
In der DbLog Datenbank gespeicherte Daten müssen bzw. sollten regelmäßig gelöscht werden sofern sie nicht mehr für Auswertungen verwendet werden. Über DbRep Kommandos gibt es dafür viele Möglichkeiten.
Vorgestellt wird hier die Möglichkeit, bereits beim Logging des Events mittels Attribut eine Device spezifische Aufbewahrungszeit festzulegen. Diese Möglichkeit ist ab der DbLog Version 5.9.4 gegeben. Die einzelnen Schritte zur Umsetzung werden nachfolgend beschrieben.
* im global Device das userattr '''retentionPeriod''' definieren. Der Name des Attributes ist natürlich frei wählbar. Das Attribut steht in allen Devices zur Verfügung und bestimmt die Aufbewahrungszeit in Sekunden wenn im Device oder im DbLog Device gesetzt.
<pre>
attr global userattr retentionPeriod
</pre>
* Für die Speicherung des Verfallzeitpunktes in der Datenbank wird die Tabellenspalte EVENT in der Tabelle history verwendet. Normalerweise wird der komplette zu loggende Event in dieser Spalte gespeichert, was im Allgemeinen nicht benötigt wird. Dazu wird in dem relevanten DbLog Device das Attribut '''valueFn''' gesetzt um in jedem zu loggenden Datensatz die Spalte EVENT die spezifische Verfallszeit zu speichern:
<pre>
{
  my $def = AttrVal ($NAME, 'retentionPeriod',  9999999999);
  $EVENT  = int(time) + AttrVal ($DEVICE, 'retentionPeriod',  $def);
}
</pre> 
* Im gleichen DbLog Device wird das Attribut '''retentionPeriod''' auf einen Default Wert gesetzt der gelten soll, wenn im zu loggenden Datensatz kein retentionPeriod-Attribut im Quellen-Device hinterlegt ist. Wenn kein retentionPeriod-Attribut im DbLog Device gesetzt ist, erfolgt mit dem Wert 'int(time) + 9999999999' de facto eine zeitlich unbegrenzte Speicherung.
<pre>
attr <DbLog> retentionPeriod 7776000    (Beispiel für Haltezeit von 90 Tagen = 7776000 Sekunden)
</pre>
[[Bild:Screenshot 2024-01-03 210126.png|right|thumb|400px|]]
* In jedem relevanten Device wird nun die gewünschte Aufbewahrungszeit in Sekunden gesetzt:
<pre>
attr <Device> retentionPeriod xxxxxxxxx
</pre>
Im Cache des DbLog Devices findet man jetzt in jedem Datensatz den Verfallszeitpunkt des Datensatzes. Er wird in der EVENT-Spalte der Tabelle gespeichert.
Vor den nächsten Schritten ist sicherzustellen, dass EVENT bei bereits vorhandenen Datensätzen auf einen passenden Verfallszeitpunkt upgedated werden.
Dazu eignet sich sqlCmd im DbRep oder ein SQL Editor deiner Wahl:
<pre>
UPDATE history SET TIMESTAMP=TIMESTAMP, EVENT="1704353074";    (setzt den Verfall auf  04.01.2024 08:24:34)
</pre>
Damit die Datenlöschungen über eine Auswertung von EVENT später performant ausgeführt werden, sollte ein ensprechender Index angelegt werden und der Datentyp von EVENT in Integer geändert werden. Beide SQL Statements können mit einem DbRep-Device mit gesetzten '''adminCredentials''' über "set ... sqlCmd" ausgeführt werden:
<pre>
CREATE INDEX Retention_Idx ON `history` (EVENT);
ALTER TABLE history MODIFY COLUMN EVENT INT(15);
</pre>
Die veränderte Spaltenbreite von EVENT wird im DbLog-Device nachgezogen und das Attribut '''colEvent''' gesetzt:
<pre>
attr <DbLog> colEvent 15
</pre>
Zur regelmäßigen Löschung der Datensätze mit einer abgelaufenen Aufbewahrungszeit dient ein at-Device in Verbindung mit einem DbRep-Device (z.B. Rep.fhemtest1) welches täglich abgelaufene Datensätze sucht und löscht.
Die Definition des at-Devices:
<pre>
define At.RetentionDel at *23:15:00 {
  my $t = int(time);
  fhem "set Rep.fhemtest1 sqlCmd delete from history where EVENT<'$t'";
}
</pre>
Das DbRep-Device benötigt keine besonderen Einstellungen außer einem gesetzten Attribut '''allowDeletion=1'''.
<pre>
define Rep.fhemtest1 DbRep <DbLog-Device>
attr Rep.fhemtest1 allowDeletion 1
attr Rep.fhemtest1 event-on-update-reading state
attr Rep.fhemtest1 room DbLog
attr Rep.fhemtest1 showproctime 1
</pre>
<br>
=== Welche Device/Reading-Kombinationen gibt es in der Datenbank ? ===
'''Hinweis:''' Im aktuellen DbRep-Release kann die nachfolgend beschriebene Auswertung alternativ durch die eingebauten Kommmandos:
set <DbRep-Device> sqlSpecial allDevCount
set <DbRep-Device> sqlSpecial allDevReadCount
ausgeführt werden.
Wenn eine Datenbank längere Zeit gelaufen ist und die zu loggenden Devices bzw. Readings immer mal wieder geändert wurden, wächst der Wunsch einen Überblick über die in der DB enthaltenen Devices/Reading-Kombinationen zu erhalten.
Dadurch können wiederum gezielt Auswertungen auf eine bestimmte Device/Reading-Kombination durchgeführt werden.
Um dieses Ziel zu erreichen, wird ein DbRep-Device erstellt welches für die Verwendung eines User-Command eingerichtet wird.
Je nach dem gewünschten Ergebnis wird eines der nachfolgenden SQL-Kommandos verwendet.
Selektion aller Device/Reading-Kombinationen in der Datenbank:
select device, reading, count(*) from history group by DEVICE, READING
Nur die geloggten Devices in der Datenbank reporten:
select device, count(*) from history group by DEVICE
Zur Einrichtung wird zunächst das DbRep-Device definiert:
define Rep.LogDB.sqlResult DbRep LogDB
Dabei ist "LogDB" '''nicht''' die Datenbank, sondern das DbLog-Device mit dem das DbRep-Device assoziiert und derüber mit der Datenbank verbunden wird !
Um die Ausgabe des späteren Ergebnisses zu formatieren wird das Attribut "sqlResultFormat" auf "table" gesetzt. Dadurch wird das Selektergebnis als Tabelle mit den Spalten Device, Reading und der Anzahl der enthaltenen Datensätze der jeweiligen Kombination erzeugt.
attr Rep.LogDB.sqlResult sqlResultFormat table
Natürlich kann auch ein anderes Ausgabeformat gewählt werden wenn gewünscht oder benötigt. Bei großen Ergebnismengen ist zur Erhöhung der Übersichtlichkeit das Attribut "sqlResultFormat = separated" wahrscheinlich eher geeignet.
Das Attribut stateFormat wird entsprechend gesetzt um nach einem erfolgten Selektionslauf die erzeugte Tabelle in der Raumansicht bzw. im DeviceOverview anzuzeigen:
<pre>
attr Rep.LogDB.sqlResult stateFormat { if (defined($defs{$name}{READINGS}{SqlResult})) {
                                            ReadingsVal($name,"SqlResult",undef);
                                        } else {
                                            ReadingsVal($name,"state",undef);
                                        }
                                      }                                       
</pre>                                 
Damit ist das DbRep-Device vorbereitet und der Auswertungslauf kann gestartet werden mit:
set Rep.LogDB.sqlResult sqlCmd select device, reading, count(*) from history group by DEVICE, READING;
bzw. wenn nur die in der Datenbank enthaltenen Devices zu erhalten:
set Rep.LogDB.sqlResult sqlCmd select device, count(*) from history group by DEVICE;
[[Datei:sqlResult.PNG|right|thumb|600px|set Rep.LogDB.sqlResult sqlCmd select device, count(*) from history group by DEVICE;]]
Nebenstehender Screenshot zeigt das Ergebnis der Auswertung aller in der Datenbank enthalteten Devices und deren Anzahl von Datensätzen.
Zum leichteren Nachnutzung hier noch die Raw-Definition des fertigen DbRep-Devices:
<pre>
defmod Rep.LogDB.sqlResult DbRep LogDB
attr Rep.LogDB.sqlResult aggregation no
attr Rep.LogDB.sqlResult comment Device zum freien Report mit sqlCmd.\
- Auswahl von Statements -\
\
Device/Reading-Kombinationen in der Datenbank:\
select device, reading, count(*) from history group by DEVICE, READING\
\
geloggte Devices in der Datenbank:\
select device, count(*) from history group by DEVICE\
attr Rep.LogDB.sqlResult devStateIcon connected:10px-kreis-gelb .*disconnect:10px-kreis-rot .*done:10px-kreis-gruen
attr Rep.LogDB.sqlResult disable 0
attr Rep.LogDB.sqlResult event-on-update-reading state
attr Rep.LogDB.sqlResult group Datenbankauswertungen
attr Rep.LogDB.sqlResult room DbLog
attr Rep.LogDB.sqlResult showproctime 1
attr Rep.LogDB.sqlResult sqlResultFormat table
attr Rep.LogDB.sqlResult stateFormat { if (defined($defs{$name}{READINGS}{SqlResult})) {\
  ReadingsVal($name,"SqlResult",undef);;\
  } else {\
  ReadingsVal($name,"state",undef);;\
  } \
}
attr Rep.LogDB.sqlResult timeout 1000
attr Rep.LogDB.sqlResult verbose 2
</pre>
=== Die current-Tabelle mit einem Extrakt der history-Tabelle füllen (tableCurrentFillup) ===
Diese Funktion erweitert die Möglichkeiten der Verwendung der current-Tabelle des DbLog-Devices. In den meisten Fällen wird im DbLog das Attribut "DbLogType = Current/History" gesetzt sein um bei der Erstellung von SVG's entsprechende Vorschläge in der DropDown-Liste des Editors zur Verfügung zu haben.
Bei dieser Betriebsart werden in der current-Tabelle neu auftretende Events von Device/Reading-Kombinationen eingetragen, sofern sie noch nicht enthalten sind. Werden neue Geräte definiert, erscheinen deren Events in der current-Tabelle, auch wenn man später nicht benötigte Readings mit den Attributen "event-on-change-reading" oder "event-on-update-reading" ausgrenzt.
Die einmal gespeicherten Werte bleiben in der current Tabelle erhalten. Sicherlich wird deswegen der eine oder andere User den Inhalt der current Tabelle löschen um wieder eine leere und saubere current-Tabelle zu haben. Das führt dann natürlich dazu, dass nur die ab diesem Zeitpunkt auftretenden Events in der Vorschlagsliste aufgenommen werden und nur selten auftretende Device/Reading-Kombinationen erst nach längerer Zeit wieder in der Tabelle current gespeichert werden.
Darüber hinaus möchte man vielleicht nur für SVG-Diagramme relevante Device/Reading-Einträge in der Vorschagsliste vorfinden oder dort nur Einträge vorhalten, die in den letzten drei Monaten aufgetreten sind. Die Beweggründe können sehr vielfältig sein.
Um diesem Wunsch zu entsprechen, gibt es im DbRep die Funktion "set <DbRep-Device> tableCurrentFillup", die eng mit dem dem DbLog-Attribut "DbLogType = SampleFill/History" zusammenarbeitet.
Nachfolgend wird an einem Beispiel erläutert, welche Konfigurationen vorgenommen werden können, um aus der history-Tabelle alle vorhandenen Device/Reading-Kombinationen zu extrahieren und sie zur Verwendung als SVG-Vorschlagsliste in die current-Tabelle einzufügen. 
Für das Beispiel wird folgendes angenommen:
* es gibt ein definiertes DbLog-Device "LogDB" welches in die Datenbank "fhem" loggt
* das DbRep-Device zur Ausführung der Funktion wird "Rep.FillCurr.fhem" genannt
* es sollen alle in der history-Tabelle vorkommenden Device/Reading-Kombinationen extrahiert und in die current-Tabelle eingetragen werden (Einschränkungen auf bestimmte Devices / Readings oder zeitliche Abgrenzungen werden nicht vorgenommen, aber es wird darauf hingewiesen, wie es gemacht werden kann)
==== a) Anlegen des DbRep-Devices ====
Die Definition des DbRep-Devices erfolgt unter Angabe des zu verbindenden DbLog-Devices (nicht der Datenbank selbst):
define Rep.FillCurr.fhem DbRep LogDB
In dem so definierten neuen Device sind die Attribute zu setzen, die für die beabsichtigte Funktion wichtig sind.
Da dieses Device auch für die Löschung der in der current-Tabelle gespeicherten Datensätze verwendet wird, muss das Attribut "allowDeletion = 1" gesetzt werden.
attr Rep.FillCurr.fhem allowDeletion 1
Ebenfalls wichtig ist die Erzeugung eines Events sobald der Löschvorgang der current-Tabelle abgeschlossen ist. Dazu wird im Beispiel "event-on-update-reading" verwendet:
attr Rep.FillCurr.fhem event-on-update-reading state,--DELETED_ROWS_CURRENT--
[[Datei:currdelete.PNG|right|thumb|300px|set Rep.FillCurr.fhem tableCurrentPurge]]
Für die Funktion nicht wichtig, aber durchaus interessant zu wissen wie lange die Laufzeit der Funktionen ist, wird noch das Attribut "showproctime = 1" gesetzt. Damit werden die Readings "background_processing_time" und "sql_processing_time" erzeugt welche die jeweilige Bearbeitungszeit in Sekunden darstellen.
attr Rep.FillCurr.fhem showproctime 1
Mit dem so vorbereiteten Device können schon die Funktionen "tableCurrentPurge" und "tableCurrentFillup" manuell getestet werden. Mit dem Kommando:
set Rep.FillCurr.fhem tableCurrentPurge
wird der gesamte aktuelle Inhalt der current-Tabelle gelöscht. Wie im nebenstehenden Screenshot sichtbar, wurden in dem Beispiel 170 Datensätze gelöscht und dazu rund 30ms benötigt. Mit Abschluß der Operation wird der Delete-Event erzeugt:
2018-01-05 13:38:14.596 DbRep Rep.FillCurr.fhem --DELETED_ROWS_CURRENT--: 170
[[Datei:currfillup.PNG|right|thumb|300px|set Rep.FillCurr.fhem tableCurrentFillup]]
Dieser Event wird später in einem Notify für die automatisierte Current-Auffüllung verwendet.
Eine Zählug der current-EInträge mit "set Rep.FillCurr.fhem countEntries current" ergibt "-", d.h. der Löschbefehl war erfolgreich. Im jetzigen Zustand würde bei der Erstellung eines SVG keine DropDown-Liste angezeigt werden und statt dessen die Felder im SVG-Editor frei bearbeitbar sein, weil die current-Tabelle keine Werte enthält.
Gleichermaßen kann bereits die Auffüllung der current-Tabelle getestet werden mit dem Befehl:
set Rep.FillCurr.fhem tableCurrentFillup
Je nach Größe der Datenbank kann diese Operation einige Zeit in Anspruch nehmen. Da dieser Befehl wie bei DbRep üblich non-blocking abgearbeitet wird, hat die Laufzeit keinen Einfluss auf die sonstige FHEM-Verfügbarkeit.
[[Datei:svgfillup.PNG|left|thumb|300px|set Rep.FillCurr.fhem tableCurrentFillup]]
Wie bereits erwähnt, wird in dem Beispiel die gesamte history-Tabelle ausgewertet. Sollen zum Beispiel nur die letzten 90 Tage zur Auswertung herangezogen werden, kann das '''Attribut "timeDiffToNow = d:90"''' gesetzt werden.
Weitere Eingrenzungen bzgl. der auszuwertenden Devices bzw. Readings können über die '''Attribute "device" und "reading"''' vorgenommen werden. Die Syntax für die genannten Attribute und deren Auswirkung entnehme bitte der {{Link2CmdRef|Lang=de|Anker=DbRepattr}}.
In unserem Beispiel wurde die current-Tabelle mit 170 Datensätzen aufgefüllt. Dazu wurden 291 Sekunden benötigt (die Tabelle history enthält insgesamt rund 11 Mio. Einträge)
Wird jetzt ein neues SVG erstellt, erscheinen alle in selektierten Device/Reading-Kombinationen alphabetisch sortiert als DopDown-Liste im SVG-Editor.
==== b) Konfiguration des DbLog-Devices ====
In dem bestehen DbLog-Device "LogDB" ist zur Vorbereitung lediglich das Attribut "DbLogType = SampleFill/History" zu setzen:
attr LogDB DbLogType SampleFill/History
Entgegen der Arebeitsweise mit "Current/History" wird die current-Tabelle nicht mehr durch die Logging-Engine aktiv mit jedem relevanten Event gefüllt, sondern kann durch externe Tools, in dem Fall dem DbRep-Device, beschrieben werden.
Die current-Tabelle wird aber, ob leer oder gefüllt, bei der Erstellung eines SVG-Devices ausgewertet und in Abhängigeit des Ergebnisses eine DropDown-Liste zur Verfügung gestellt, oder frei editierbare Felder angeboten falls die current-Tabelle leer ist.
==== c) Definition der at/notify-Devices zum automatisierten Ablauf ====
Die Löschung des Inhaltes der current-Tabelle und die erneute Ausffüllung soll natürlich autmatisiert regelmäßig erfolgen. Zu diesem Zweck wird ein at-Device (At.LogDB.currentPurge) und ein notify-Device angelegt. Das AT-Device soll alle 8 Stunden und 15 Minuten das Löschen des current-Inhaltes steuern und nach Abschluß der Löschung soll getriggert durch das "--DELETED_ROWS_CURRENT--"-Event die Neubefüllung der current-Tabelle veranlasst werden.
Zur Definition dieser Devices gibt es nicht viel zu erläutern. Die entsprechende Syntax kann bei {{Link2CmdRef|Lang=de|Anker=at|Label=at}} bzw. {{Link2CmdRef|Lang=de|Anker=notify|Label=notify}} in der {{Link2CmdRef|Lang=de}} nachgelesen werden.
'''at-Device:'''
define At.LogDB.currentPurge at +*08:15:00 set Rep.FillCurr.fhem tableCurrentPurge
'''notify-Device:'''
define N.LogDB.Fillup notify Rep.FillCurr.fhem:.*DELETED_ROWS_CURRENT.*  sleep 5;set Rep.FillCurr.fhem tableCurrentFillup
Das nichtblockierende FHEM-sleep (sleep gefolgt von einem weiteren Befehl) wird lediglich dazu verwendet, um zwischen der Delete- und Auffüll-Operation eine kleine Pause einzufügen.
==== d) Arbeitsweise und Raw-Definitionen ====
Durch das "At.LogDB.currentPurge"-Device wird alle 8 Stunden und 15 Minuten ein Löschlauf der current-Tabelle gestartet. Dadurch wird dem darauf folgenden Auffüllprozess immer eine saubere leere current-Tabelle zur Verfügung gestellt. Nach dem Löschlauf wird durch das "N.LogDB.Fillup"-Device, getriggert durch den Event "Rep.FillCurr.fhem:.*DELETED_ROWS_CURRENT.*", die erneute Auffüllung der current-Tabelle gestartet.
Die Daten werden aus der histrory-Tabelle gelesen, wobei der Zeitraum der Selektion und die zu betrachtenden Devices / Readings durch die Zeit-Attribute bzw. device / reading-Attribute des DbRep-Devices "Rep.FillCurr.fhem" individuell beeinflußt werden können.
Hilfreich ist es auch, wenn das DbLog-Device "LogDB" im asynchronen Modus betrieben wird.
Abschließend sind hier noch die Raw-Definitionen der beteiligten Devices (außer LogDB) angehängt, um das Beispiel leicht nachvollziehbar zu gestalten.
'''DbRep-Device "Rep.FillCurr.fhem":'''
<pre>
defmod Rep.FillCurr.fhem DbRep LogDB
attr Rep.FillCurr.fhem allowDeletion 1
attr Rep.FillCurr.fhem comment Current Fillup für DB fhem
attr Rep.FillCurr.fhem devStateIcon connected:10px-kreis-gelb .*disconnect:10px-kreis-rot .*done:10px-kreis-gruen
attr Rep.FillCurr.fhem event-on-update-reading state,--DELETED_ROWS_CURRENT--
attr Rep.FillCurr.fhem icon icoTool
attr Rep.FillCurr.fhem room DbLog
attr Rep.FillCurr.fhem showproctime 1
attr Rep.FillCurr.fhem verbose 3
</pre>
'''at-Device "At.LogDB.currentPurge":'''
<pre>
defmod At.LogDB.currentPurge at +*08:15:00 set Rep.FillCurr.fhem tableCurrentPurge
attr At.LogDB.currentPurge comment Zeitgesteuertes Löschen der Current Tabelle. Nach dem Löschen wird \
Eventgesteuert (Notify N.LogDB.Fillup) ein tableCurrentFillup gestartet.
attr At.LogDB.currentPurge room DbLog
</pre>
'''notify-Device "N.LogDB.Fillup":'''
<pre>
defmod N.LogDB.Fillup notify Rep.FillCurr.fhem:.*DELETED_ROWS_CURRENT.*  sleep 5;;set Rep.FillCurr.fhem tableCurrentFillup
attr N.LogDB.Fillup disable 1
attr N.LogDB.Fillup room DbLog
attr N.LogDB.Fillup verbose 2
</pre>
=== Backup/Restore einer SQLite Datenbank im laufenden Betrieb ===
In diesem Beitrag wird erläutert, wie man mit DbRep ein Backup der SQLite-Datenbank erstellen kann, welche Möglichkeiten es dabei gibt und wie man eine Datenbank aus einem Backup wieder herstellen kann.
Die Backup-Funktion nutzt die SQLite Online Backup API und ermöglicht es, konsistente Backups der SQLite-DB in laufenden Betrieb zu erstellen ohne die Datenbank schließen zu müssen.
Neben dem eigentlichen Backup kann optional einstellt werden, dass:
* vor dem Backup eine Datenbankoptimierung (Vacuum) ausgeführt werden soll. Dadurch wird Plattenplatz freigegeben sofern möglich.
* vor und nach dem Backup kann ein FHEM-Kommando oder eine Perl-Routine ausgeführt werden (eine Perl-Routine ist in {} einzuschließen)
* das erstellte Dumpfile kann über FTP(S) an einen FTP-Server übertragen werden
* über die interne Versionsverwaltung kann festgelegt werden, wieviele erstellte Backups auf dem Datenträger erhalten bleiben sollen (default: 3)
Für die Erläuterung des Beispiels soll ein DbRep-Device erstellt werden, welches:
* die SQLite Datenbank im Betrieb (Online) sichert
* vor dem Dump einen Vacuum-Lauf durchführt
* das erstellte Dumpfile auf einen FTP-Server überträgt
* 2 Versionen (Dumpfiles) nach einem erfolgreichen Backuplauf im Dumpverzeichnis verbleiben sollen
==== 1. Anlegen des DbRep-Devices ====
Das anzulegende Device wird sowohl für das (regelmäßige) Backup als auch für einen eventuellen Restore verwendet.
Die Definition des DbRep-Devices erfolgt unter Angabe des zu verbindenden DbLog-Devices (nicht der Datenbank selbst):
define Rep.SQLite DbRep LogSQLITE
Ist das Device definiert, verbindet es sich zu der Datenbank und wechselt in den state "connected", sofern die Verbindung erfolgreich verlief.
Prinzipiell funktioniert der hier beschriebene Backup-Prozess mit einem im synchronous Mode betriebenen DbLog-Device, aber es ist dringend angeraten das DbLog-Device (LogSQLITE) in den asynchronen Modus umzuschalten. Dadurch werden Blockierungen von FHEM und Verluste von Daten verhindert.
==== 2. Einstellungen des DbRep-Devices (Attribute) ====
Für die gewünschte Funktion müssen in diesem neu definierten Device einige relevante Attribute gesetzt werden.
:'''a) dumpDirLocal'''
Das zu erstellende Backupfile wird per default im log-Verzeichnis ./log (meist /opt/fhem/log) des FHEM-Rechners gespeichert. Um es an einer anderen Stelle zu speichern kann das Attribut "dumpDirLocal" gesetzt werdden. Dieses Verzeichnis kann sich auf dem lokalen Datenträger befinden, oder ein per NFS gemountetes Verzeichnis sein. In jedem Fall muss der User unter dem FHEM läuft Schreibrechte auf dieses Verzeichnis besitzen.
Für das Beispiel sollen die Dump-Files auf einen NFS-Mount einer Synology Diskstation gespeichert werden. Dazu wurde ein Mount in /etc/fstab erstellt um den Pfad "/sds1/backup" lokal verfügbar zu machen:
sds1.<fqdn>:/volume1/ApplicationBackup /sds1/backup nfs auto,defaults,tcp,intr 0 0
In diesem Verzeichnis exsitiert das Directory "dumps_FHEM", in dem die Backup-Files gespeichert werden sollen. Das Attribut wird entsprechend gesetzt auf:
attr Rep.SQLite dumpDirLocal /sds1/backup/dumps_FHEM
:'''b) dumpFilesKeep'''
Dieser Parameter stellt die Anzahl der aufzubewahrenden Backup-Files ein. Es werden immer die "x" neuesten bzw. letzten Files im Verzeichnis gehalten, wobei nur die zu der jeweiligen Datenbank gehörenden Files betrachtet werden.
Die Zugehörigkeit des Dump-Files zur Quelldatenbank wird anhand des Filenamens ermittelt, der sich zusammensetzt aus <DB-Name ohne Endung>_<Erstellungsdatum und Uhrzeit>.sqlitebkp
Beispiel: fhem_2018_01_12_17_15.sqlitebkp
Ist dieses Attribut nicht gesetzt, werden die 3 neuesten Backup-Files im Verzeichnis behalten.
Um nur 2 Files zu behalten wird das Attribut gesetzt auf:
attr Rep.SQLite dumpFilesKeep 2
:'''c) Aktionen vor und nach dem Backup ausführen'''
Vor und nach der Dump-Ausführung kann ein FHEM-Kommando bzw. Perl-Befehle ausgeführt werden. Perl-Befehle müssen in {} eingeschlossen werden.
Für das Beispiel soll vor dem Backup die Verbindung des DbLog-Devices LogSQLITE zur Datenbank getrennt werden. Dadurch werden keine neuen Daten in die Datenbank geschrieben. Wenn das DbLog-Device LogSQLITE im asynchronen Modus wie empfohlen betrieben wird, tritt auch kein Datenverlust ein, da die zu loggenden Daten im Cache verbleiben bis die Verbindung zur DB wieder hergestellt wird.
Es sei noch einmal darauf hingewiesen, dass das Backup-Verfahren ebenso im synchronen Modus und ohne das DbLog-Device von der Datenbank zu trennen, funktioniert und nur im Beispiel zur Demonstation der Möglichkeiten dient.
Um die Verbindung zu trennen wird ein "set LogSQLITE reopen <Zeit in Sekunden>" verwendet. Die Zeitspanne wird sehr großzügig gewählt und soll sicherstellen, dass innerhalb dieser Zeit das Backup abgeschlossen ist. Nach dem Backup wird sofort ein "set LogSQLITE reopen" ausgeführt, was die Verbindung des DbLog-Devices LogSQLITE zur Datenbank unmittelbar wieder herstellt..
attr Rep.SQLite executeBeforeProc set LogSQLITE reopen 3600
attr Rep.SQLite executeAfterProc set LogSQLITE reopen
:'''d) vor dem Backup Datenbank verkleinern (vacuum)'''
Diese optionale Funktion wird durch das Attribut "optimizeTablesBeforeDump" eingeschaltet. Dadurch die Vacuum-Funktion wird Platz innerhalb der Datenbank freigegeben der durch Löschvorgänge entstanden ist und dadurch die Größe des Datenbankfiles verringert.
attr Rep.SQLite optimizeTablesBeforeDump 1
:'''e) das Dump-File nach dem Backup zum FTP-Server übertragen'''
DbRep bietet die Möglichkeit, das erstellte Dump-File per FTP oder verschlüsselt per FTP(S) zum Server zu übertragen. Dazu gibt es einen Satz von Attributen um dem Device alle notwendigen Angaben für den FTP-Transfer zur Verfügung zu stellen. Es gibt dafür folgende Attribute:
* ftpUse : FTP Transfer nach dem Dump wird eingeschaltet (bzw. ftpUseSSL mit SSL Verschlüsselung)
* ftpUser : User zur Anmeldung am FTP-Server, default: anonymous
* ftpPwd : Passwort des FTP-Users, default nicht gesetzt
* ftpDir : Verzeichnis auf dem FTP-Server in welches das File übertragen werden soll (default: FTP-root)
* ftpPort : FTP-Port, default: 21
* ftpServer : Name oder IP-Adresse des FTP-Servers
* ftpTimeout : Timeout für die FTP-Verbindung in Sekunden (default: 30)
* ftpPassive : setzen wenn passives FTP verwendet werden soll
* ftpDebug : Debugging des FTP Verkehrs zur Fehlersuche
Gemäß dieser Beschreibung und dem Ziel dieses Beispiels werden die für FTP-Transfer relevanten Attribute wie folgt gesetzt:
attr Rep.SQLite ftpDir /ftp                # Subdirectory ftp unter FTP-root als Zielverzeichnis
attr Rep.SQLite ftpPwd ftpftp1
attr Rep.SQLite ftpServer sds1.<fqdn>
attr Rep.SQLite ftpUse 1
attr Rep.SQLite ftpUser ftpuser
Der FTP-Server muss natürlich vorab eingerichtet und funktionsfähig sein. Sollten beim FTP-Transfer Fehler auftreten, kann das Attribut ftpDebug = 1 gesetzt werden um entsprechende Ausgaben zur Analyse des Problems im Log zu erhalten.
[[Datei:repsqlite_defined.PNG|right|thumb|300px|Fertig definiertes Device Rep.SQLite]]
:'''f) Hilfsattribute'''
Für die Funktion nicht relevant aber informativ ist das Attribut "showproctime = 1". Ist das Attribut gesetzt, wird das Reading "background_processing_time" angelegt. Es enthält nach der Ausführung die verbrauchte Prozesszeit.
Das fertig konfigurierte Device hier als RAW-Definition zur einfachen Nachnutzung. Die Angaben sind natürlich anzupassen:
<pre>
define Rep.SQLite DbRep LogSQLITE
attr Rep.SQLite devStateIcon connected:10px-kreis-gelb .*disconnect:10px-kreis-rot .*done:10px-kreis-gruen
attr Rep.SQLite dumpDirLocal /sds1/backup/dumps_FHEM
attr Rep.SQLite dumpFilesKeep 2
attr Rep.SQLite event-on-update-reading state
attr Rep.SQLite executeAfterProc set LogSQLITE reopen
attr Rep.SQLite executeBeforeProc set LogSQLITE reopen 3600
attr Rep.SQLite ftpDir /ftp
attr Rep.SQLite ftpPwd ftpftp1
attr Rep.SQLite ftpServer sds1.<fqdn>
attr Rep.SQLite ftpUse 1
attr Rep.SQLite ftpUser ftpuser
attr Rep.SQLite optimizeTablesBeforeDump 1
attr Rep.SQLite room DbLog
attr Rep.SQLite showproctime 1
attr Rep.SQLite verbose 3
</pre>
<br>
==== 3. Backup durchführen ====
Mit dem wie beschrieben eingerichteten DbRep-Device kann das Buckup gestartet werden mit:
set Rep.SQLite dumpSQLite
[[Datei:logsqlite_closeduntil.PNG|right|thumb|500px|LogSQLITE ist geschlossen bis ...]]
Zu Beginn des Vorgangs wird sofort die Verbindung des DbLog-Devices zur Datenbank getrennt.
Ein laufendes Backup kann mit "set Rep.SQLite cancelDump" abgebrochen werden.
Das laufende Backup wird im state angezeigt mit "SQLite Dump is running - be patient and see Logfile !". Dieser Satz ist ernst gemeint, wobei der Dump über die Online-API sehr schnell arbeitet. Im Logfile mit (mindestens) verbose 3 werden die relevanten Informationen zur Verfügung gestellt:
<pre>
2018.01.13 08:35:10.745 3: DbRep Rep.SQLite - ################################################################         
2018.01.13 08:35:10.746 3: DbRep Rep.SQLite - ###                    New SQLite dump                      ###
2018.01.13 08:35:10.747 3: DbRep Rep.SQLite - ################################################################
2018.01.13 08:35:10.748 3: DbRep Rep.SQLite - execute command before dump: 'set LogSQLITE reopen 3600'
2018.01.13 08:35:10.883 2: DbLog LogSQLITE: Connection closed until 09:35:10 (3600 seconds).
2018.01.13 08:35:10.917 3: DbRep Rep.SQLite - Size of database /opt/fhem/fhem.db before optimize (MB): 2554
2018.01.13 08:35:10.918 3: DbRep Rep.SQLite - VACUUM database /opt/fhem/fhem.db....
2018.01.13 08:44:04.216 3: DbRep Rep.SQLite - Size of database /opt/fhem/fhem.db after optimize (MB): 2554
2018.01.13 08:44:04.280 3: DbRep Rep.SQLite - Starting dump of database 'fhem.db'
2018.01.13 08:45:03.810 3: DbRep Rep.SQLite - Size of backupfile: 2553.64 MB
2018.01.13 08:45:04.579 3: DbRep Rep.SQLite - FTP: transferring /sds1/backup/dumps_FHEM/fhem_2018_01_13_08_44.sqlitebkp
2018.01.13 08:45:48.838 3: DbRep Rep.SQLite - FTP: fhem_2018_01_13_08_44.sqlitebkp transferred successfully to sds1.myds.me into dir /ftp
2018.01.13 08:45:48.844 3: DbRep Rep.SQLite - Deleting old dumpfile 'fhem_2018_01_13_08_13.sqlitebkp'
2018.01.13 08:45:49.273 3: DbRep Rep.SQLite - Finished backup of database fhem - total time used: 638 seconds
2018.01.13 08:45:49.358 2: DbRep Rep.SQLite - command after dump message: "Reopen executed."
2018.01.13 08:45:49.370 3: DbRep Rep.SQLite - Database dump finished successfully.
</pre>
[[Datei:repsqlite_dumprunning.PNG|right|thumb|400px|das Backup läuft]]
Setzt man das Attribut "ftpDebug=1", erhält man im Log zusätzliche Informationen zum FTP-Transfer:
<pre>
2018.01.13 08:50:22.385 3: DbRep Rep.SQLite - Size of backupfile: 2553.82 MB
Net::FTP>>> Net::FTP(2.79)
Net::FTP>>>  Exporter(5.72)
Net::FTP>>>  Net::Cmd(2.30)
Net::FTP>>>  IO::Socket::INET(1.35)
Net::FTP>>>    IO::Socket(1.38)
Net::FTP>>>      IO::Handle(1.35)
Net::FTP=GLOB(0xb12ed20)<<< 220 SDS1 FTP server ready.
Net::FTP=GLOB(0xb12ed20)>>> USER ftpuser
Net::FTP=GLOB(0xb12ed20)<<< 331 Password required for ftpuser.
Net::FTP=GLOB(0xb12ed20)>>> PASS ....
Net::FTP=GLOB(0xb12ed20)<<< 230 User ftpuser logged in, access restrictions apply.
Net::FTP=GLOB(0xb12ed20)>>> TYPE I
Net::FTP=GLOB(0xb12ed20)<<< 200 Type set to I.
Net::FTP=GLOB(0xb12ed20)>>> CWD /ftp
Net::FTP=GLOB(0xb12ed20)<<< 250 CWD command successful.
2018.01.13 08:50:22.880 3: DbRep Rep.SQLite - FTP: transferring /sds1/backup/dumps_FHEM/fhem_2018_01_13_08_49.sqlitebkp
Net::FTP=GLOB(0xb12ed20)>>> PORT 192,168,2,45,145,42
Net::FTP=GLOB(0xb12ed20)<<< 200 PORT command successful.
Net::FTP=GLOB(0xb12ed20)>>> ALLO 2677867520
Net::FTP=GLOB(0xb12ed20)<<< 202 ALLO command ignored.
Net::FTP=GLOB(0xb12ed20)>>> STOR fhem_2018_01_13_08_49.sqlitebkp
Net::FTP=GLOB(0xb12ed20)<<< 150 Opening BINARY mode data connection for 'fhem_2018_01_13_08_49.sqlitebkp'.
Net::FTP=GLOB(0xb12ed20)<<< 226 Transfer complete.
2018.01.13 08:51:06.859 3: DbRep Rep.SQLite - FTP: fhem_2018_01_13_08_49.sqlitebkp transferred successfully to sds1.myds.me into dir /ftp
</pre>
[[Datei:repsqlite_dumpfinished.PNG|right|thumb|500px|Backup ist erfolgreich abgeschlossen]]
In vorliegenden Beispiel wird nach einem erfolgreichen Backup im state "Warning - dump finished, but command after dump message appeared" angezeigt. Diese Warnung rührt daher, dass das Reopen-Kommando eine Rückkehrinfo mitteilt, die als Problem gewertet und im Reading "afterdump_message" ausgegeben wird. In diesem Fall ist es nur eine Information.
In den erstellten Readings wird zusammengetragen welches File mit welcher Größe ertsellt wurde, welche alten Files gelöscht wurden, Informationen zum FTP-Transfer und welche Zeit der Gesamtprozess benötgt hat.
Das so vorbereitete Backup kann nun z.B. täglich oder auch mit einer wesentlich kürzeren Wiederholungsperiode (alle x Stunden) über ein at-Device eingeplant werden:
define At.SQLite.Dump at *23:52:00 set Rep.SQLite dumpSQLite
==== 4. Restore ====
Sollte es einmal zur Korruption des Datenbankfiles kommen (database disk image is malformed) und Reparaturversuche erfolglos bleiben, kann ein Restore die einzigste oder vielleicht auch einfachste Möglichkeit sein die Datenbank wieder herzustellen.
Das kann mit dem angelegten DbRep-Device im laufenden Betrieb geschehen. Auch hier ist wieder wichtig, dass das DbLog-Device LogSQLITE im asynchronen Modus betrieben wird und die Reopen-Zeit (siehe Einstellung Attribut "executeBeforeProc") sehr großzügig eingestellt ist.
Zum Restore gibt es den Befehl "set ... restoreSQLite <File>".
[[Datei:sqliterestore_auswahl.PNG|right|thumb|500px|Auswahlmenü Files für Restore ]]
Im FHEMWEB öffnet sich dazu eine DropDown-Liste die alle vorhandenen und zur Quelldatenbank passenden Dumpfiles auflistet. Das herzustellende File
kann so bequem ausgewählt werden. Die Dumpfiles müssen sich in dem Verzeichnis befinden, welches durch das Attribut "dumpDirLocal" festgelegt wurde (default (./log).  [[Datei:sqliterestore_running.PNG|left|thumb|500px|Restore läuft]]
Der restore wird demzufolge gestartet mit:
set Rep.SQLite restoreSQLite <File>
Wieder wird zu Beginn des Vorgangs das verbundene DbLog-Device von der Datenbank abgekoppelt und nach dem Restore wieder automatisch verbunden. Eine Datenbankoptimierung bzw. FTP-Transfer wird bei diesem Vorgang natürlich nicht ausgeführt.
Das Log zeigt den Prozessverlauf:
<pre>
2018.01.13 09:21:55.435 3: DbRep Rep.SQLite - ################################################################
2018.01.13 09:21:55.437 3: DbRep Rep.SQLite - ###            New database Restore/Recovery                ###
2018.01.13 09:21:55.437 3: DbRep Rep.SQLite - ################################################################
2018.01.13 09:21:55.438 3: DbRep Rep.SQLite - execute command before restore: 'set LogSQLITE reopen 3600'
2018.01.13 09:21:55.448 2: DbLog LogSQLITE: Connection closed until 10:21:55 (3600 seconds).
2018.01.13 09:21:55.477 3: DbRep Rep.SQLite - Starting restore of database 'fhem.db'
2018.01.13 09:30:12.533 3: DbRep Rep.SQLite - Restore of /sds1/backup/dumps_FHEM/fhem_2018_01_13_08_49.sqlitebkp into 'fhem.db' finished - total time used: 497 seconds.
2018.01.13 09:30:12.685 2: DbRep Rep.SQLite - command after restore message: "Reopen executed."
2018.01.13 09:30:12.700 3: DbRep Rep.SQLite - Database restore finished successfully.
</pre>
Der Restore wurde erfolgreich abgeschlossen und die Verbindung des DbLog-Device LogSQLITE zur Datenbank wiederhergestellt. Das Logging wird nahtlos fortgeführt.
[[Datei:sqliterestore_finished.PNG|right|thumb|300px|Restore erfolgreich abgeschlossen]]
Es ist natürlich zu beachten, dass die Daten zwischen dem Erstellungszeitpunkt des eingespielten Backups und der aktuellen Zeit verloren sind !
Die bestehende (korrupte) Datenbank wird überschrieben. Deswegen ist es ratsam immer ein aktuelles (zeitnahes) Backup zu haben um den Datenverlust möglichst gering zu halten.
==== 5. Links ====
* Diskussion im Forum: "{{Link2Forum|Topic=82674|LinkText=Erstellung konsistentes Online-Backup im laufenden Betrieb}}"
=== Backup/Restore einer MySQL/MariaDB Datenbank im laufenden Betrieb ===
Das Backup einer MySQL/MariaDB Datenbank (im Folgenden stellvertretend als MySQL bezeichnet) kann als Varianten
* clientSide
* serverSide
Beim '''clientSide''' Backup werden die Daten über SQL-Statements aus der Datenbank gelesen, auf dem Client (dem FHEM-Server) verarbeitet und das Dumpfile geschrieben. Es werden history- und current-Tabelle gesichert, sowie eventuell weitere angelegte Tabellen und Views. Allerdings benötigt dieser Modus umfangreichere RAM und CPU-Ressorcen auf dem Client.
Die '''serverSide''' Option wird nachfolgend beispielhaft genauer erläutert.
==== serverSide Backup Option ====
Neben dem eigentlichen Backup kann optional einstellt werden, dass:
* vor dem Backup eine Tabellenoptimierung (optimizeTables) ausgeführt werden soll. Dadurch wird Plattenplatz freigegeben sofern möglich.
* vor und nach dem Backup kann ein FHEM-Kommando oder eine Perl-Routine ausgeführt werden (eine Perl-Routine ist in '''{ }''' einzuschließen)
* das erstellte Dumpfile kann komprimiert werden
* das erstellte Dumpfile kann über FTP(S) an einen FTP-Server übertragen werden
* über die interne Versionsverwaltung kann die Anzahl der im Backupverzeichnis zu verbleibenden Backup-Files festgelegt werden (default: 3)
Für die Erläuterung des Beispiels soll ein DbRep-Device erstellt werden, welches:
* die MySQL Datenbank im Betrieb (Online) sichert
* vor dem Dump die history-Tabelle optimiert
* das erstellte Dumpfile auf einen FTP-Server überträgt
* drei Versionen (Dumpfiles) nach der Übertragung auf dem FTP-Server belässt, ältere Files werden vom FTP-Server gelöscht
* eine Version (Dumpfile) nach einem erfolgreichen Backuplauf im Dumpverzeichnis verbleiben sollen
<br>
===== 1. Anlegen des DbRep-Devices =====
Das anzulegende Device wird sowohl für das (regelmäßige) Backup als auch für ein eventuelles Restore verwendet.
Die Definition des DbRep-Devices erfolgt unter Angabe des zu verbindenden DbLog-Devices (nicht der Datenbank selbst):
define Rep.fhemtest.Dump.ServerSide DbRep LogDB
Ist das Device definiert, verbindet es sich zu der Datenbank und wechselt in den state "connected", sofern die Verbindung erfolgreich verlief.
Prinzipiell funktioniert der hier beschriebene Backup-Prozess mit einem im synchronous Mode betriebenen DbLog-Device, aber es ist dringend angeraten das DbLog-Device (LogDB) in den asynchronen Modus umzuschalten. Dadurch werden FHEM-Blockierungen vermieden.
<br>
===== 2. Einstellungen des DbRep-Devices (Attribute) =====
Für die gewünschte Funktion müssen in diesem neu definierten Device einige relevante Attribute gesetzt werden.
:'''a) dumpDirRemote'''
Der Dump wird durch den MySQL-Server erstellt und per default im Home-Verzeichnis des MySQL-Servers gespeichert.
Es wird die gesamte history-Tabelle (nicht current-Tabelle) im '''CSV-Format''' ohne Einschränkungen exportiert.
Um es an einer anderen Stelle zu speichern, wird das Attribut "dumpDirRemote" gesetzt.
Auch wenn der MySQL-Server mit auf dem FHEM-Server läuft, also lokal, wird dieses Attribut zur Veränderung des Zielverzeichnisses verwendet.
<br>
:'''b) dumpDirLocal'''
Soll die interne Versionsverwaltung und die Dumpfilekompression des Moduls genutzt, sowie die Größe des erzeugten Dumpfiles ausgegeben werden, ist das Verzeichnis "dumpDirRemote" des MySQL-Servers auf dem Client zu mounten und im Attribut "dumpDirLocal" dem DbRep-Device bekannt zu machen.
Gleiches gilt wenn der FTP-Transfer nach dem Dump genutzt werden soll (Attribut "ftpUse" bzw. "ftpUseSSL").
Im Beispiel läuft der MySQL-Server auf einem entfernten Server und die Dump-Files werden dort im Verzeichnis
/volume1/ApplicationBackup
angelegt. Dieses Verzeichnis soll auf dem FHEM-Server als Verzeichnis "/sds1/backup" gemountet werden.
Wenn noch nicht passiert, den NFS Client auf dem FHEM Server installieren:
sudo apt-get update
sudo apt-get install nfs-common
Auf dem entfernten Server wird das Verzeichnis freigegeben/exportiert.
Wenn noch nicht vorhanden, ist zunächst das notwendige Paket zu installieren:
sudo apt-get install nfs-kernel-server
Ist das Paket vorhanden, existiert die Datei '''/etc/exports'''.
In der Datei '''/etc/exports''' wird das zu exportierende Verzeichnis hinzugefügt. Aus Gründen der Sicherheit wird nur die IP-Adresse des FHEM Servers für den Zugriff zugelassen:
  /volume1/ApplicationBackup 192.168.50.33(rw,root_squash,async,no_subtree_check)
Soll ein Netzwerk freigegeben werden, kann der Eintrag in in dieser Form erfolgen:
/volume1/ApplicationBackup 192.168.XXX.0/24(rw,root_squash,async,no_subtree_check)
Danach Datei /etc/exports neu einlesen:
sudo exportfs -ra
Auf dem Client Rechner wird der Mountpoint für das einzuhängende Verzeichnus erstellt:
sudo mkdir /sds1/backup
Einen Eintrag in '''/etc/fstab''' erstellen:
<IP-Adresse>:/volume1/ApplicationBackup /sds1/backup nfs auto,defaults,tcp,intr 0 0
Ausführen:
mount /sds1/backup
In diesem Verzeichnis existiert das Directory "dumps_FHEM", in dem die Backup-Files gespeichert werden sollen. Das Attribut wird entsprechend gesetzt auf:
attr Rep.fhemtest.Dump.ServerSide dumpDirLocal /sds1/backup/dumps_FHEM
'''Hinweis:''' <br>
Läuft der MySQL-Server mit FHEM lokal auf dem gleichen Server, zeigen die Attribute '''dumpDirRemote und dumpDirLocal''' auf das '''identische Verzeichnis'''. Trotzdem sind beide Attribute zu definieren.
<br>
:'''b) dumpFilesKeep'''
Dieser Parameter stellt die Anzahl der aufzubewahrenden Backup-Files ein. Es werden immer die "x" neuesten bzw. letzten Files im Verzeichnis gehalten, wobei nur die zu der jeweiligen Datenbank gehörenden Files betrachtet werden.
Die Zugehörigkeit des Dump-Files zur Quelldatenbank wird anhand des Filenamens ermittelt.
Er setzt sich zusammen aus <Datenbankname>_history_<Erstellungsdatum_Uhrzeit>.csv
Beispiel: fhemtest_history_2020_05_14_03_42.csv (+.gzip falls Dumpfiles komprimiert werden)
Ist dieses Attribut nicht gesetzt, werden die 3 neuesten Backup-Files im Verzeichnis behalten.
Um nur 2 Files zu behalten wird das Attribut gesetz:
attr Rep.fhemtest.Dump.ServerSide dumpFilesKeep 2
:'''c) Aktionen vor und nach dem Backup ausführen'''
Vor und nach der Dump-Ausführung kann ein FHEM-Kommando bzw. Perl-Befehle ausgeführt werden. Perl-Befehle müssen in '''{ }''' eingeschlossen werden.
Für das Beispiel soll vor dem Backup die Verbindung des DbLog-Devices LogDB zur Datenbank getrennt werden. Dadurch werden keine neuen Daten in die Datenbank geschrieben. Wenn das DbLog-Device LogDB im asynchronen Modus wie empfohlen betrieben wird, tritt auch kein Datenverlust ein, da die zu loggenden Daten im Cache verbleiben bis die Verbindung zur DB wiederhergestellt wird.
Um die Verbindung zu trennen wird ein
set LogDB reopen <Zeit in Sekunden>
verwendet. Die Zeitspanne wird sehr großzügig gewählt und soll sicherstellen, dass innerhalb dieser Zeit das Backup abgeschlossen ist. Nach dem Backup wird sofort
set LogDB reopen
ausgeführt, was die Verbindung des DbLog-Devices LogDB zur Datenbank unmittelbar wiederherstellt.
attr Rep.fhemtest.Dump.ServerSide executeBeforeProc set LogDB reopen 3600
attr Rep.fhemtest.Dump.ServerSide executeAfterProc set LogDB reopen
:'''d) vor dem Backup Datenbank verkleinern (optimizeTables)'''
Diese optionale Funktion wird durch das Attribut "optimizeTablesBeforeDump" eingeschaltet. Dadurch diese Funktion wird Platz innerhalb der Datenbank freigegeben der durch Löschvorgänge entstanden ist und dadurch die Größe des Datenbankfiles verringert.
attr Rep.fhemtest.Dump.ServerSide optimizeTablesBeforeDump 1
Durch die Tabellenoptimierung verlängert sich die Dumpzeit.
:'''e) das angelegte Dump-File nach dem Backup zum FTP-Server übertragen'''
DbRep bietet die Möglichkeit, das erstellte Dump-File per FTP oder verschlüsselt per FTPS zum FTP-Server zu übertragen. Dazu gibt es einen Satz von Attributen um dem Device alle notwendigen Angaben für den FTP-Transfer zur Verfügung zu stellen. Es gibt dafür folgende Attribute:
* ftpUse : FTP Transfer nach dem Dump wird eingeschaltet (bzw. ftpUseSSL mit SSL Verschlüsselung)
* ftpUser : User zur Anmeldung am FTP-Server, default: anonymous
* ftpPwd : Passwort des FTP-Users, default nicht gesetzt
* ftpDir : Verzeichnis auf dem FTP-Server in welches das File übertragen werden soll (default: FTP-root)
* ftpPort : FTP-Port, default: 21
* ftpServer : Name oder IP-Adresse des FTP-Servers
* ftpTimeout : Timeout für die FTP-Verbindung in Sekunden (default: 30)
* ftpPassive : setzen wenn passives FTP verwendet werden soll
* ftpDebug : Debugging des FTP Verkehrs zur Fehlersuche
Gemäß dieser Beschreibung und dem Ziel dieses Beispiels werden die für FTP-Transfer relevanten Attribute wie folgt gesetzt:
attr Rep.fhemtest.Dump.ServerSide ftpDir /ftp                # Subdirectory ftp unter FTP-root als Zielverzeichnis
attr Rep.fhemtest.Dump.ServerSide ftpPwd ftpftp1
attr Rep.fhemtest.Dump.ServerSide ftpServer sds1.<fqdn>
attr Rep.fhemtest.Dump.ServerSide ftpUse 1
attr Rep.fhemtest.Dump.ServerSide ftpUser ftpuser
Der FTP-Server muss natürlich vorab eingerichtet und funktionsfähig sein. Sollten beim FTP-Transfer Fehler auftreten, kann das Attribut ftpDebug = 1 gesetzt werden um entsprechende Ausgaben zur Analyse des Problems im Log zu erhalten.
[[Datei:dumpmysql1.PNG|right|thumb|300px|Fertig definiertes Device Rep.fhemtest.Dump.ServerSide]]
<br>
:'''f) Hilfsattribute'''
Für die Funktion nicht relevant aber informativ ist das Attribut "showproctime = 1". Ist das Attribut gesetzt, wird das Reading "background_processing_time" angelegt. Es enthält nach der Ausführung die verbrauchte Prozesszeit.
Im Attribut "userExitFn" kann eine Perl-Routine hinterlegt werden, um zum Beispiel nach dem Backup eine Mail oder Telegram-Message mit dem Erfolgsstatus zu versenden.
Das fertig konfigurierte Device hier als RAW-Definition zur einfachen Nachnutzung. Die Angaben sind natürlich anzupassen:
<pre>
define Rep.fhemtest.Dump.ServerSide DbRep LogDB
attr Rep.fhemtest.Dump.ServerSide devStateIcon connected:10px-kreis-gelb .*disconnect:10px-kreis-rot .*done:10px-kreis-gruen .*Dump.*is.*running.*:remotecontrol/black_btn_PLAYgreen Database.*backup.*finished.*:remotecontrol/black_btn_GREEN tn_GREEN error.*:remotecontrol/black_btn_RED
attr Rep.fhemtest.Dump.ServerSide dumpCompress 1
attr Rep.fhemtest.Dump.ServerSide dumpDirLocal /sds1/backup/dumps_FHEM
attr Rep.fhemtest.Dump.ServerSide dumpDirRemote /volume1/ApplicationBackup/dumps_FHEM
attr Rep.fhemtest.Dump.ServerSide dumpFilesKeep 1
attr Rep.fhemtest.Dump.ServerSide event-on-update-reading state
attr Rep.fhemtest.Dump.ServerSide executeAfterProc set LogDB reopen
attr Rep.fhemtest.Dump.ServerSide executeBeforeProc set LogDB reopen 3600
attr Rep.fhemtest.Dump.ServerSide fastStart 1
attr Rep.fhemtest.Dump.ServerSide ftpDebug 0
attr Rep.fhemtest.Dump.ServerSide ftpDir /ftp
attr Rep.fhemtest.Dump.ServerSide ftpDumpFilesKeep 3
attr Rep.fhemtest.Dump.ServerSide ftpPwd ftpftp1
attr Rep.fhemtest.Dump.ServerSide ftpServer sds1.<fqdn>
attr Rep.fhemtest.Dump.ServerSide ftpUse 0
attr Rep.fhemtest.Dump.ServerSide ftpUser ftpuser
attr Rep.fhemtest.Dump.ServerSide optimizeTablesBeforeDump 1
attr Rep.fhemtest.Dump.ServerSide showproctime 1
attr Rep.fhemtest.Dump.ServerSide userExitFn doafterdump
</pre>
<br>
===== 3. Backup durchführen =====
Voraussetzung ist, dass der verwendete Datenbankuser das globale Privileg '''FILE''' besitzt.
Falls der User dieses Recht nicht hat, kann man es mit dem definierten DbRep-Device wie folgt erledigen.
:'''(optional) dem Datenbankuser das FILE Privileg zuweisen'''
Da die Rechtezuweisung nur mit einem administrativen DB-User (i.A. root) funktioniert, wird dieser User im DbRep-Device angelegt und aktiviert:
<pre>
set  Rep.fhemtest.Dump.ServerSide adminCredentials <Admin-User> <Passwort>
attr Rep.fhemtest.Dump.ServerSide useAdminCredentials 1
</pre>
Nun kann dem verwendeten Datenbankuser das FILE Privileg zugeordnet werden:
<pre>
set  Rep.fhemtest.Dump.ServerSide sqlCmd GRANT FILE ON *.* TO '<DB-User>'@'%';
</pre>
Dabei ist '''<DB-User>''' durch den in der DbLog-Konfiguration angegebenen User zu ersetzen da die gesamte Datenbankarbeit mit diesem User erfolgt.
Nach der Ausführung schaltet man die Nutzung des administrativen Users wieder ab mit:
<pre>
attr Rep.fhemtest.Dump.ServerSide useAdminCredentials 0
</pre>
Die Rechte können nun überprüft werden mit
<pre>
set  Rep.fhemtest.Dump.ServerSide sqlCmd show grants;
</pre>
und werden im Ergebnis dargestellt (z.B. für den DB-User fhemtest):
<pre>
SqlResultRow_1  GRANTS FOR FHEMTEST@%
SqlResultRow_2  GRANT PROCESS, FILE, INDEX ON *.* TO 'fhemtest'@'%' IDENTIFIED BY PASSWORD '*...............'
SqlResultRow_3  GRANT SELECT, INSERT, UPDATE, DELETE, ALTER, EXECUTE, SHOW VIEW ON `fhemtest`.* TO 'fhemtest'@'%'
SqlResultRow_4  GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, INDEX ON `fhemtest`.`fhemtest` TO 'fhemtest'@'%'
</pre>
<br>
:'''Backup starten'''
[[Datei:dumpmysql2.PNG|right|thumb|400px|Backup läuft ...]]
Mit dem wie beschrieben eingerichteten DbRep-Device kann das Backup gestartet werden mit:
set Rep.fhemtest.Dump.ServerSide dumpMySQL serverSide
Zu Beginn des Vorgangs wird sofort die Verbindung des DbLog-Devices zur Datenbank getrennt.
Ein laufendes serverSide Backup kann mit FHEM-Mitteln nicht abgebrochen werden !
Das laufende Backup wird im state angezeigt mit "serverSide Dump is running - be patient and see Logfile !". 
Im Logfile mit (mindestens) verbose 3 werden die relevanten Informationen zur Verfügung gestellt:
<pre>
2020.05.14 22:59:18.610 3: DbRep Rep.fhemtest.Dump.ServerSide - ########################################
2020.05.14 22:59:18.611 3: DbRep Rep.fhemtest.Dump.ServerSide - ###  New database serverSide dump  ###
2020.05.14 22:59:18.611 3: DbRep Rep.fhemtest.Dump.ServerSide - ########################################
2020.05.14 22:59:18.612 3: DbRep Rep.fhemtest.Dump.ServerSide - execute command before dump: 'set LogDB reopen 3600'
2020.05.14 22:59:18.613 2: DbLog LogDB: Connection closed until 23:59:18 (3600 seconds).
2020.05.14 22:59:18.660 3: DbRep Rep.fhemtest.Dump.ServerSide - Searching for tables inside database fhemtest....
2020.05.14 22:59:18.664 3: DbRep Rep.fhemtest.Dump.ServerSide - Size of database fhemtest before optimize (MB): 8298.98
2020.05.14 22:59:18.665 3: DbRep Rep.fhemtest.Dump.ServerSide - Optimizing tables
2020.05.14 22:59:18.665 3: DbRep Rep.fhemtest.Dump.ServerSide - Optimizing table `current` (INNODB). It will take a while.
2020.05.14 22:59:19.560 3: DbRep Rep.fhemtest.Dump.ServerSide - Table 1 `current` optimized successfully.
2020.05.14 22:59:19.560 3: DbRep Rep.fhemtest.Dump.ServerSide - Optimizing table `history` (INNODB). It will take a while.
2020.05.14 23:30:09.183 3: DbRep Rep.fhemtest.Dump.ServerSide - Table 2 `history` optimized successfully.
2020.05.14 23:30:09.186 3: DbRep Rep.fhemtest.Dump.ServerSide - 2 tables have been optimized.
2020.05.14 23:30:09.253 3: DbRep Rep.fhemtest.Dump.ServerSide - Size of database fhemtest after optimize (MB): 8298.98
2020.05.14 23:30:09.254 3: DbRep Rep.fhemtest.Dump.ServerSide - Starting dump of database 'fhemtest', table 'history'
2020.05.14 23:39:47.566 3: DbRep Rep.fhemtest.Dump.ServerSide - compress file /sds1/backup/dumps_FHEM/fhemtest_history_2020_05_14_23_30.csv
2020.05.14 23:42:33.138 3: DbRep Rep.fhemtest.Dump.ServerSide - file compressed to output file: /sds1/backup/dumps_FHEM/fhemtest_history_2020_05_14_23_30.csv.gzip
2020.05.14 23:42:34.784 3: DbRep Rep.fhemtest.Dump.ServerSide - input file deleted: /sds1/backup/dumps_FHEM/fhemtest_history_2020_05_14_23_30.csv
2020.05.14 23:42:34.785 3: DbRep Rep.fhemtest.Dump.ServerSide - Number of exported datasets: 49032159
2020.05.14 23:42:34.787 3: DbRep Rep.fhemtest.Dump.ServerSide - Size of backupfile: 419.73 MB
2020.05.14 23:42:37.082 3: DbRep Rep.fhemtest.Dump.ServerSide - FTP: transferring /sds1/backup/dumps_FHEM/fhemtest_history_2020_05_14_23_30.csv.gzip
2020.05.14 23:42:47.511 3: DbRep Rep.fhemtest.Dump.ServerSide - FTP: fhemtest_history_2020_05_14_23_30.csv.gzip transferred successfully to sds1.myds.me into dir /ftp
2020.05.14 23:42:47.558 3: DbRep Rep.fhemtest.Dump.ServerSide - Deleting old dumpfile 'fhemtest_history_2020_05_14_22_12.csv.gzip'
2020.05.14 23:42:47.857 3: DbRep Rep.fhemtest.Dump.ServerSide - Finished backup of database fhemtest - total time used (hh:mm:ss): 00:43:29
2020.05.14 23:42:47.948 2: DbRep Rep.fhemtest.Dump.ServerSide - command message after dump: "Reopen executed."
2020.05.14 23:42:47.973 3: DbRep Rep.fhemtest.Dump.ServerSide - Database dump finished successfully.
</pre>
[[Datei:dumpmysql3.PNG|right|thumb|500px|Backup ist erfolgreich abgeschlossen]]
In vorliegenden Beispiel wird nach einem erfolgreichen Backup im state '''"Warning - dump finished, but command after dump message appeared"''' angezeigt.
Diese Warnung rührt daher, dass das Reopen-Kommando eine Rückkehrinfo mitteilt, die als Issue gewertet und im Reading "afterdump_message" ausgegeben wird. In diesem Fall ist es nur eine Information.
In den erstellten Readings wird zusammengetragen welches File mit welcher Größe erstellt wurde, welche alten Files gelöscht wurden, Informationen zum FTP-Transfer und welche Zeit der Gesamtprozess benötgt hat.
Das so vorbereitete Backup kann nun z.B. täglich oder auch mit einer wesentlich kürzeren Wiederholungsperiode (alle x Stunden) über ein at-Device eingeplant werden:
define At.MySQL.Dump at *23:52:00 set Rep.fhemtest.Dump.ServerSide dumpMySQL serverSide
<br>
===== 4. Restore =====
Voraussetzung für das Restore ist, dass der verwendete Datenbankuser das globale Privileg '''FILE''' besitzt.
Falls der User dieses Recht nicht hat, kann man es mit dem definierten DbRep-Device wie folgt erledigen.
:'''(optional) dem Datenbankuser das FILE Privileg zuweisen'''
Da die Rechtezuweisung nur mit einem administrativen DB-User (i.A. root) funktioniert, wird dieser User im DbRep-Device angelegt und aktiviert:
<pre>
set  Rep.fhemtest.Dump.ServerSide adminCredentials <Admin-User> <Passwort>
attr Rep.fhemtest.Dump.ServerSide useAdminCredentials 1
</pre>
Nun kann dem verwendeten Datenbankuser das FILE Privileg zugeordnet werden:
<pre>
set  Rep.fhemtest.Dump.ServerSide sqlCmd GRANT FILE ON *.* TO '<DB-User>'@'%';
</pre>
Dabei ist '''<DB-User>''' durch den in der DbLog-Konfiguration angegebenen User zu ersetzen da die gesamte Datenbankarbeit mit diesem User erfolgt.
Nach der Ausführung schaltet man die Nutzung des administrativen Users wieder ab mit:
<pre>
attr Rep.fhemtest.Dump.ServerSide useAdminCredentials 0
</pre>
'''Alternativ''' kann die Rechtezuweisung auf der Konsole des MySQL-Servers ausgeführt werden:
<pre>
sudo mysql -u root -p
GRANT File ON *.* TO '<DB-User>'@'%';
exit
</pre>
Die Rechte können nun überprüft werden mit
<pre>
set  Rep.fhemtest.Dump.ServerSide sqlCmd show grants;
</pre>
Nun kann der Restore ausgeführt werden.
Ein mit der Option '''serverSide''' erstellter Dump enthält ausschließlich die Daten der history-Tabelle.
Der Restore kann nur in eine bestehende Datenabank und entsprechend angelegte history Tabelle erfolgen.
Ist nach einem Datenbankfehler die Tabelle/Datenbank zerstört, must sie zunächst mit den vorbereiteten Befehlen in den
[https://svn.fhem.de/trac/browser/trunk/fhem/contrib/dblog/db_create_mysql.sql SVN-Skripten] bereitgestellt werden.
Ist die history-Tabelle noch intakt und soll nur der Inhalt ersetzt werden, muss die Tabelle vorab geleert werden da sonst unter Umständen doppelte Datensätze enstehen wenn kein primary Key verwendet ist.
Das kann einfach mit dem Befehl
set <Name> sqlCmd truncate table history
erreicht werden. Damit werden '''alle''' Daten der history-Tabelle hochperformant gelöscht. Die Tabelle selbst bleibt erhalten.
Sollte der truncate Befehl wegen nicht ausreichender Rechte des DB-Users mit Fehler enden, kann ein administrativer Datenbankuser (i.A. 'root') mit dem Kommando und Attribut
set  <Name> adminCredentials <Name> <Passwort>
attr <Name> useAdminCredentials 1
hinterlegt und aktiviert werden.
Ein Restore kann mit dem angelegten DbRep-Device im laufenden Betrieb geschehen. Auch hier ist wieder wichtig, dass das DbLog-Device  im asynchronen Modus betrieben wird und die Reopen-Zeit (siehe Einstellung Attribut "executeBeforeProc") sehr großzügig eingestellt ist.
Zum Restore dient der Befehl
set <Name> restoreMySQL <File>
[[Datei:dumpmysql4.PNG|right|thumb|500px|Auswahlmenü Files für Restore ]]
Im FHEMWEB öffnet sich dazu eine DropDown-Liste die alle vorhandenen und zur Quelldatenbank passenden Dumpfiles auflistet.
Das herzustellende File kann so bequem ausgewählt werden. Die Dumpfiles müssen sich in dem Verzeichnis befinden, welches durch das Attribut '''dumpDirLocal''' festgelegt wurde (default (./log).  [[Datei:dumpmysql5.PNG|left|thumb|500px|Restore läuft]]
Der restore wird demzufolge gestartet mit:
set Rep.fhemtest.Dump.ServerSide restoreMySQL <File>
Der Restore der history-Tabelle kann nun online im laufenden FHEM-Betrieb erfolgen.
Wieder wird zu Beginn des Vorgangs das verbundene DbLog-Device von der Datenbank abgekoppelt und nach dem Restore wieder automatisch verbunden. Eine Datenbankoptimierung bzw. FTP-Transfer wird bei diesem Vorgang natürlich nicht ausgeführt.
[[Datei:dumpmysql6.PNG|left|thumb|500px|Fortschrittsanzeige in einem zweiten DbRep Device]]
In einem zweiten (z.B. kopierten) DbRep Device kann mit Hilfe des Befehls
get Rep.fhemtest.Dump.ServerSide procinfo
der Fortschritt des Restores überwacht werden. In der Spalte '''PROGRESS''' wird der Fortschritt in '''%''' angegeben.
Das Log zeigt den Ablauf des Restores:
<pre>
2020.05.15 13:07:24.369 3: DbRep Rep.fhemtest.Dump.ServerSide - #######################################
2020.05.15 13:07:24.370 3: DbRep Rep.fhemtest.Dump.ServerSide - ###  New database Restore/Recovery ###
2020.05.15 13:07:24.371 3: DbRep Rep.fhemtest.Dump.ServerSide - #######################################
2020.05.15 13:07:24.371 3: DbRep Rep.fhemtest.Dump.ServerSide - execute command before restore: 'set LogDB reopen 3600'
2020.05.15 13:07:24.429 3: DbRep Rep.fhemtest.Dump.ServerSide - uncompress file /sds1/backup/dumps_FHEM/fhemtest_history_2020_05_15_11_47.csv.gzip
2020.05.15 13:09:31.152 3: DbRep Rep.fhemtest.Dump.ServerSide - file uncompressed to output file: /sds1/backup/dumps_FHEM/fhemtest_history_2020_05_15_11_47.csv
2020.05.15 13:09:31.237 3: DbRep Rep.fhemtest.Dump.ServerSide - Size of uncompressed file: 5511.52 MB
2020.05.15 13:09:31.242 3: DbRep Rep.fhemtest.Dump.ServerSide - Starting restore of database 'fhemtest', table 'history'.
2020.05.15 14:42:28.566 2: DbLog LogDB: Connection closed until 16:42:28 (7200 seconds).
2020.05.15 14:46:22.391 3: DbRep Rep.fhemtest.Dump.ServerSide - Restore of /volume1/ApplicationBackup/dumps_FHEM/fhemtest_history_2020_05_15_11_47.csv into 'fhemtest', 'history' finished - total time used (hh:mm:ss): 01:38:57
2020.05.15 14:46:22.457 2: DbRep Rep.fhemtest.Dump.ServerSide - command message after restore: "Reopen executed."
2020.05.15 14:46:22.481 3: DbRep Rep.fhemtest.Dump.ServerSide - Database restore finished successfully.
</pre>
[[Datei:dumpmysql7.PNG|right|thumb|300px|Restore erfolgreich abgeschlossen]]
Der Restore wurde erfolgreich abgeschlossen und die Verbindung des DbLog-Device LogDB zur Datenbank wiederhergestellt. Das Logging wird nahtlos fortgeführt.
Es ist natürlich zu beachten, dass die Daten zwischen dem Erstellungszeitpunkt des eingespielten Backups und der aktuellen Zeit verloren sind !
Eventuell in der Tabelle '''history''' noch vorhandene Daten werden '''nicht''' überschrieben.
<br>
=== Speichern von Berechnungswerten in der Datenbank und Erstellen eines Plots (ab Version 7.5.1) ===
Es sollen aus in der Datenbank vorhandenen minütlichen Leistungswerten eines Wechselrichters die maximal-, minimal- und durchschnittlichen Leistungswerte pro Tag berechnet werden und diese Ergebnisse wieder in die Datenbank geschrieben werden um daraus einen Plot zu erstellen.
Diese Berechnung soll immer aktuell für den laufenden Monat erfolgen.
Die Ausgangswerte liegen in der Datenbank als minütliche kW-Einträge vor (Auszug DbRep fetchrows):
<pre>
2018-01-18_15-55-49__MySTP_5000__total_pac 0.200
2018-01-18_15-56-50__MySTP_5000__total_pac 0.218
2018-01-18_16-00-14__MySTP_5000__total_pac 0.209
2018-01-18_16-00-39__MySTP_5000__total_pac 0.198
2018-01-18_16-01-39__MySTP_5000__total_pac 0.142
2018-01-18_16-02-39__MySTP_5000__total_pac 0.130
2018-01-18_16-03-39__MySTP_5000__total_pac 0.117
2018-01-18_16-05-39__MySTP_5000__total_pac 0.087
2018-01-18_16-06-39__MySTP_5000__total_pac 0.071
2018-01-18_16-07-39__MySTP_5000__total_pac 0.076
2018-01-18_16-08-39__MySTP_5000__total_pac 0.065
2018-01-18_16-09-39__MySTP_5000__total_pac 0.069
2018-01-18_16-10-39__MySTP_5000__total_pac 0.060
2018-01-18_16-12-39__MySTP_5000__total_pac 0.065
2018-01-18_16-13-39__MySTP_5000__total_pac 0.068
2018-01-18_16-14-39__MySTP_5000__total_pac 0.054
2018-01-18_16-15-39__MySTP_5000__total_pac 0.041
2018-01-18_16-16-39__MySTP_5000__total_pac 0.027
2018-01-18_16-17-39__MySTP_5000__total_pac 0.026
2018-01-18_16-18-39__MySTP_5000__total_pac 0.021
2018-01-18_16-19-39__MySTP_5000__total_pac 0.018
2018-01-18_16-20-39__MySTP_5000__total_pac 0.012
</pre>
Für die Berechnung der Ergenisse stehen in DbRep die Kommandos maxValue, minValue und averageValue zur Verfügung.
<br>
==== 1. Anlegen des DbRep-Devices ====
Das anzulegende Device wird für alle notwendigen Berechnungen verwendet.
Die Definition des DbRep-Devices erfolgt unter Angabe des zu verbindenden DbLog-Devices (nicht der Datenbank selbst):
define Rep.total_pac DbRep LogDB
Ist das Device definiert, verbindet es sich zu der Datenbank und wechselt in den state "connected", sofern die Verbindung erfolgreich verlief.
Prinzipiell funktioniert der hier Ablauf mit einem im synchronous Mode betriebenen DbLog-Device, aber es ist dringend angeraten das DbLog-Device (LogDB) in den asynchronen Modus umzuschalten. Dadurch werden Blockierungen von FHEM verhindert.
<br>
==== 2. Einstellungen des DbRep-Devices (Attribute) ====
[[Datei:Rep.total_pac.PNG|right|thumb|400px|Device definiert]]
Für die gewünschte Funktion müssen in diesem neu definierten Device einige relevante Attribute gesetzt werden.
Da die Berechnungen immer für den aktuellen Monat erstellt werden sollen, wird das Attribut "timestamp_begin" so definiert, dass immer der Beginn des aktuellen Monats dynamisch ermittelt wird. Das ist gegeben durch den Eintrag:
attr Rep.total_pac timestamp_begin current_month_begin
Es soll ein Ergebniswert pro Tag erzeugt werden. Damit dies erreicht werden kann, wird das Attribut "aggregation" verwendet und auf den Wert "day" eingestellt. Würde zum Beispiel ein stündlicher Ergebniswert gewünscht sein, hätte "aggregation = hour" den benötigten Effekt.
attr Rep.total_pac aggregation day
Die Attribute "device" und "reading" legen das auszuwertende Device und Reading fest. Dabei ist zu beachten, dass im Gegensatz zur allgemein gültigen Möglichkeit devspec bzw. SQL-Wildcards zu verwenden bei dieser speziellen Detailfunktion dies nicht der Fall ist. Das heißt. es muß immer ein eindeutiges Device und ein eindeutiges Reading angegeben werden. Der Grund liegt in der Notwendigkeit, bei der Speicherung der Ergebnisdaten der Datenbank ebenfalls ein eindeutiges Device und Reading zu übergeben. In dem Beispiel wird das Device "MySTP_5000" und das Reading "total_pac" ausgewertet.
attr Rep.total_pac device MySTP_5000
attr Rep.total_pac reading total_pac
Hilfreiche Attribute sind noch "event-on-update-reading" zur Begrenzung der Events, "showproctime" zur Bestimmung der Prozesszeiten und "allowDeletion" um die Möglichkeit zu haben, irrtümlich gespeicherte Berechnungsergebnisse wieder über das DbRep-Device zu löschen.
attr Rep.total_pac event-on-update-reading state
attr Rep.total_pac showproctime 1
attr Rep.total_pac allowDeletion 1
Das so vorbereitete Device ist für die benötigten Funktionen ausreichend eingestellt.
Zur einfachen Nachnutzung nochfolgend die Raw-Definition des DbRep-Devices:
<pre>
define Rep.total_pac DbRep LogDB
attr Rep.total_pac aggregation day
attr Rep.total_pac allowDeletion 1
attr Rep.total_pac device MySTP_5000
attr Rep.total_pac event-on-update-reading state
attr Rep.total_pac reading total_pac
attr Rep.total_pac room DbLog
attr Rep.total_pac showproctime 1
attr Rep.total_pac timestamp_begin current_month_begin
attr Rep.total_pac verbose 3
</pre>
<br>
==== 3. Berechnung ausführen und Speicherung der Ergebnisse ====
[[Datei:average.PNG|right|thumb|300px|Average Berechnung]]
Die benötigten Funktionen sind:
averageValue - Berechnung des täglichen Durchschnittswert der Wechselrichterleistung (kW)
maxValue - Berechnung des täglichen Maximalwertes der Wechselrichterleistung (kW)
minValue - Berechnung des täglichen Minimalwertes der Wechselrichterleistung (kW)
[[Datei:minval.PNG|right|thumb|300px|MinVal Berechnung]]
Für jede dieser Kommandos stehen im DbRep die Optionen "display" und "writeToDB" zur Verfügung. Die Option "display" stellt die Ergenisse lediglich im Browser dar und kann dazu die Ergebnisse vor der Speicherung zu bewerten.
So ergeben die Kommandos:
set Rep.total_pac minValue display
set Rep.total_pac maxValue display
set Rep.total_pac avarageValue display
die täglichen Min-, Max- und Durchschnittswerte der WR-Leistung.
[[Datei:maxval.PNG|right|thumb|300px|MaxVal Berechnung]]
Entsprechen die Ergebnisse den Erwartungen bzw. dem Ziel, können die folgenden Kommandos verwendet werden um die Berechnung durchzuführen und dabei gleichzeitig in die Datenbank zu schreiben.
set Rep.total_pac minValue writeToDB
set Rep.total_pac maxValue writeToDB
set Rep.total_pac avarageValue writeToDB
Die Daten werden in der history-Tabelle gespeichert und, sofern das DbLog-Device das Attribut "DbLogType = Current..." gestzt hat, auch in der current-Tabelle gespeichert. Im letzteren Fall kann das Ergebnisreading bei der Erstellung des Plots sofort aus der DropDown-Liste ausgewählt werden.
Bei der Datenspeicherung wird der neue Readingnamen aus einem Präfix und dem originalen Readingnamen gebildet. Der Präfix setzt sich aus der Bildungsfunktion und der gewählten Aggregation zusammen, d.h. im Fall des Beispiels aus "min","max","avg" und "day".
Der Timestamp der neuen Readings in der Datenbank wird von der eingestellten Aggregationsperiode abgeleitet, sofern kein eindeutiger Zeitpunkt des Ergebnisses bestimmt werden kann. So kann der Zeitpunkt des Maximalwertes eines Tages wertes genau bestimmt werden und der resultierende Datensatz wird mit diesem Timestamp gespeichert.
Die resultierende Readingnamen werden somit wie folgt in der Datenbank gespeichert:
max_day_total_pac
min_day_total_pac
avg_day_total_pac
Die Berechnungen können regelmäßig über ein at eingeplant werden. Die Funktionen vermeiden dabei die Speicherung von doppelten Datensätzen. Wurde bei einem Berechnungslauf der Datensatz bereits gespeichert, wird dieser Datensatz beim nächsten Durchlauf nicht noch einmal gespeichert.
==== 4. Erstellung des Plots ====
[[Datei:dropdown.PNG|right|thumb|300px|Drop-Down Liste SVG]]
Wird durch das DbLog-Device die current-Tabelle genutzt, das Attribut "DbLogType = Current..." ist gesetzt, können die Ergebnisreadings max_day_total_pac, min_day_total_pac und avg_day_total_pac im SVG-Editor ausgewählt werden (siehe {{Link2CmdRef|Lang=de|Anker=SVG}} bei SVG).
Durch die Wahl des Plot-Typ "bars" und dem SVG-Attribut "fixedrange = month" entsteht der gewünschte Tageswert-Plot über den aktuellen Monat.
[[Datei:svg_calc.PNG|right|thumb|300px|Ergebnis SVG]]
Hier wiederum die Raw-Definition des SVG-Device:
<pre>
defmod SVG_LogDB_16 SVG LogDB:SVG_LogDB_16:HISTORY
attr SVG_LogDB_16 fixedrange month
attr SVG_LogDB_16 room Energie,SVG_MySQL
attr SVG_LogDB_16 title "Max Leistung: $data{max1} kW, Min Leistung: $data{min3} kW,Max. Durchschnittsleistung: $data{max2} kW "
</pre>
sowie des gplot-Files:
<pre>
# Created by FHEM/98_SVG.pm, 2018-01-18 21:45:27
set terminal png transparent size <SIZE> crop
set output '<OUT>.png'
set xdata time
set timefmt "%Y-%m-%d_%H:%M:%S"
set xlabel " "
set title '<TL>'
set ytics
set y2tics
set grid
set ylabel "kW"
set y2label "kW"
set yrange [0:6]
set y2range [0:6]
#LogDB MySTP_5000:max_day_total_pac
#LogDB MySTP_5000:avg_day_total_pac
#LogDB MySTP_5000:min_day_total_pac
plot "<IN>" using 1:2 axes x1y2 title 'Max-Leistung / Tag' ls l0fill lw 1 with bars,\
    "<IN>" using 1:2 axes x1y2 title 'Average / Tag' ls l2fill lw 1 with bars,\
    "<IN>" using 1:2 axes x1y2 title 'Min-Leistung / Tag' ls l1fill lw 1 with bars
</pre>
<br>
=== Öffnungszeiten von Fenster/Türen aus der Datenbank ermitteln ===
Ziel ist es, die Öffnungszeit eines Fensters oder einer Tür zu ermitteln sobald es wieder geschlossen wird und ein Reading mit der Zeit in dem entsprechenden Device (Fensterkontakt) zu setzen.
Für das Beispiel wird angenommen dass:
* der Status der Sensoren als "open" und "close" in der Datenbank gespeichert ist
* das verwendetet DbRep-Device "Rep.WindowOpenTime" heißt
* das verwendete DbLog-Device "LogDBShort" heißt (wird mit dem DbRep-Device bei der Definition assoziiert)
Zunächst wird zur Auswertung von closed-Events ein entsprechendes Notify definiert (Raw-Format):
<pre>
defmod N.WindowClosed notify (.*fenster.*|.*terrasse.tuer.*):closed \
  { \
    if($NAME =~ /(.*fenster.*|.*terrasse.tuer.*)/) {\
      fhem("set LogDBShort commitCache;; defmod calcse at +00:00:10 {SwitchEvent(\"$NAME\")};;");; \
    }\
  }
attr N.WindowClosed alias Starte Ermittlung der Fenster Öffnungszeiten
attr N.WindowClosed comment Wird verwendet um die letzte Öffnungszeit der Fenster aus der Datenbank zu ermitteln und im Reading "LastOpenTime" abzulegen.
attr N.WindowClosed room Datenbank->Produktiv
</pre>
Die zusätzlich if-Abfrage im Notify behob ein Problem bei den Fensterkontakten die mit einem Thermostat gepeert sind, da in diesem Fall der Event mehrfach auftrat. Der Teil '''"set LogDBShort commitCache"''' speichert den Cache der Datenbank vor der Berechnung sofern sie im asynchronen Modus betrieben wird (kann entfallen wenn im synchronen Mode betrieben). Um der DB Zeit zur Abspeicherung zu geben, wird die Berechnung über ein AT mit einer entsprechenden Verzögerung (hier 10 Sekunden) angestartet. Die Zeit im AT bitte entsprechend der eigenen Systembedingungen anpassen. 
Im Notify wird die Funktion '''SwitchEvent($NAME)''' aufgerufen. Diese Funktion wird in der 99_myUtils.pm eingefügt:
<syntaxhighlight lang="perl">
##########################################################################################################
#  This function is used to start an SQL query for the given device "$name" and check
#  how much time the device was in state defined by $usedVal1 today.
#  The query is done by a DbRep device.
#
#  In this example the name of the DbRep device is "Rep.WindowOpenTime"
##########################################################################################################
sub SwitchEvent($) {
  my ($name) = @_;
  Log3 $name , 5, "$name - SwitchEvent called";
  my $usedReading = "\"state\"";          # name of the reading which holds the state which has to be checked
  my $usedVal1 = "\"open\"";              # reading value which starts the time measuerement
  my $usedVal2 = "\"closed\"";            # reading value which stops the time measurement
  my $device = "\"$name\"";                # name of the device which state has to be checked
  my $dbRepDevice = "Rep.WindowOpenTime";  # name of the DbRep device
  # The query will check for the current day the cumulated time difference between $usedVal1 and $usedVal2
  # the example here is a homemetic threeStateSensor (window contact) and the result of the query is
  # the cumulated time which the window was open (in seconds) and the name of the device whic is
  # used by the function SwitchQueryResult()
  # Example:  AZ_Fensterkontakt|433.2
  my $sql = "SET \@topen = NULL,\@closed = NULL, \@popen = NULL;;".
            " SELECT  DEVICE ,SUM(TIMEDIFF(tclosed, topen)) AS duration FROM (".
            " SELECT TIMESTAMP, VALUE, DEVICE,".
            " \@topen  := \@popen AS topen,".
            " \@closed  := IF(VALUE = $usedVal2,  TIMESTAMP, NULL) AS tclosed,".
            " \@popen  := IF(VALUE = $usedVal1, TIMESTAMP, NULL) AS prev_open".
            " FROM history WHERE".
            " DATE(TIMESTAMP) = CURDATE() AND".
            " DEVICE = $device AND".
            " READING = $usedReading AND ".
            " (VALUE = $usedVal1 OR VALUE = $usedVal2)".
            " ORDER BY  TIMESTAMP".
            " ) AS tmp";
  Log3 $name , 5, "$name - SwitchEvent start query: $sql";
  fhem("set $dbRepDevice sqlCmd $sql"); # start the query now. Result will be processed in the function SwitchQueryResult() below
return;
}
</syntaxhighlight>
Die Funktion '''SwitchEvent()''' bekommt den Namen des Gerätes übergeben welches Event ausgelöst hat. Damit wird eine SQL-Abfrage für dieses Gerät gestartet und ermittelt,  wieviel Zeit (in Sekunden) für das übergebene Gerät zwischen dem Zustand "open" und "closed" verbracht wurde.
Das verwendete DbRep-Device wird wie folgt definiert:
<pre>
define Rep.WindowOpenTime DbRep LogDBShort
attr Rep.WindowOpenTime aggregation no
attr Rep.WindowOpenTime comment Öffnungszeit der Fenster ermitteln
attr Rep.WindowOpenTime initialized:control_3dot_hor_s connected:10px-kreis-gelb .*disconnect:10px-kreis-rot .*done:10px-kreis-gruen
attr Rep.WindowOpenTime reading state
attr Rep.WindowOpenTime showproctime 1
attr Rep.WindowOpenTime sqlResultFormat sline
attr Rep.WindowOpenTime event-on-update-reading state
attr Rep.WindowOpenTime userExitFn SwitchQueryResult
</pre>
Ganz wichtig ist die Zeile '''attr Rep.powerOnTime userExitFn SwitchQueryResult'''. Hier ist die Funktion '''SwitchQueryResult()''' angegeben, die nach erfolgreicher Bearbeitung der SQL-Query aufgerufen wird. Diese Funktion wird ebenfalls in der 99_myUtils.pm eingefügt:
<syntaxhighlight lang="perl">
##########################################################################################################
# This function will be called from the DbRep device if the attribute
# attr Name_of_your_DbRep_device userExitFn SwitchQueryResult is set.
# This function create a new reading "LastOpenTime" at the device for which the query is done and
# set the cumulated time difference in seconds to the reading.
##########################################################################################################
sub SwitchQueryResult($$$) {
  my ($name,$reading,$value) = @_;  # $name is the name of the DbRep device which is calling this function
  my $hash = $defs{$name};
  # DbRep calls this function several times with different informations.
  # if $reading is SqlResult, $value will hold the result of the query
  if ($reading eq "SqlResult") {
      my ($dev,$val) = split('\|',$value);                  # split the result in two parts
      $dev = $dev?$dev:"";
      $val = $val?$val:"";
      if (!$val) {
          $val = 0;
      }
      $val = DbRep_sec2hms($val);                            # calculate seconds to hms
      Log3 $name , 5, "DbRep $name - SwitchQueryResult splitted result: $dev $val";
      fhem("setreading $dev LastOpenTime $val") if($dev);    # set the reading "LastOpenTime" with the time if device-Log was found
  }
return;
}
</syntaxhighlight>
Die Funktion SwitchQueryResult() rechnet das Ergebnis der query aus dem DbRep Device von Sekunden in das Format hh:mm:ss um und trägt die berechnete Zeit als Reading "'''LastOpenTime'''" im jeweiligen Fensterkontakt ein.
So kann man z.B. im Device mit dem Namen "eg.bad.fenster" das Reading "'''LastOpenTime 00:03:58'''" finden.
Die vorliegende Berechnung liefert immer die Zeit des letzten Öffnungszyklus des aktuellen Tages. Möchte man die Zeiten des gesamten Tages akkumulieren, kann nach der Beschreibung "[[Summe aller Einschaltzeiten eines Gerätes]]" verfahren werden.
=== Grünlandtemperatursumme ermitteln und in SVG-Grafik darstellen ===
Nachfolgendes Beispiel zeigt, wie die Grünlandtemperatursumme ermittelt werden kann und die Ermittlungsergebnisse in der Datenbank gespeichert werden, um sie in einer SVG-Grafik darzustellen. Das beschriebene Verfahren kann natürlich auch für andere Anwendungsfälle angewendet werden.
Was ist die Grünlandtemperatursumme?
Hier die Definition aus [https://de.wikipedia.org/wiki/Grünlandtemperatursumme Wikipedia]:
: ''Die Grünlandtemperatursumme (GTS) ist eine Spezialform der Wachstumsgradtage, die in der Agrarmeteorologie verwendet wird. Sie wird herangezogen, um in Mitteleuropa den Termin für das Einsetzen der Feldarbeit nach dem Winter zu bestimmen.''
: ''Eine Wärmesumme ist allgemein eine gewisse Lufttemperatur eines Tages über die Tage einer Periode summiert. Dabei verwendet man besonders die kumulierte korrigierte GTS, die nach Monat gewichtet wird.''
: ''Wird im Frühjahr die Summe von 200 überschritten, ist der nachhaltige Vegetationsbeginn erreicht. Hintergrund ist die Stickstoffaufnahme und -verarbeitung des Bodens, welcher von dieser Temperatursumme abhängig ist. In mittleren Breiten wird das meist im Laufe des März, an der Wende von Vorfrühling zu Mittfrühling erreicht. ''
Zur Bestimmung der GTS benötigt man zunächst die Aufzeichnung der örtlichen Temperaturwerte in einer DbLog-Datenbank.
Im Beispiel wird dazu das Reading '''temperature''' des Device '''MyWetter''' (Device vom Typ [[Weather]]) genutzt. Die Einrichtung des Weather- und DbLog-Devices wird an dieser Stelle nicht beschrieben und als bekannt vorausgesetzt.
Es wird angenommen, dass:
* das definierte DbLog-Device im asynchronen Mode betrieben wird
* das Reading '''temperature''' des Device '''MyWetter''' in der DbLog-Datenbank gespeichert wird
Zur Ermittlung des GTS wird ein DbRep-Device mit gesetztem [[Attribut]] '''avgDailyMeanGWSwithGTS''' verwendet. Dieses Attribut legt die Berechnung der täglichen Durchschnittstemperatur nach den Regularien des Deutschen Wetterdienstes fest und aktiviert auf dieser Grundlage weiterhin die Berechnung der Grünlandtemperatursumme.
[[Datei:gts1.PNG|right|thumb|400px|GTS Ermittlung]]
Die RAW-Definition des verwendeten DbRep-Devices:
<pre>
define Rep.GTS DbRep LogDB
attr Rep.GTS averageCalcForm avgDailyMeanGWSwithGTS
attr Rep.GTS device MyWetter
attr Rep.GTS fastStart 1
attr Rep.GTS reading temperature
attr Rep.GTS room DbLog
attr Rep.GTS showproctime 1
attr Rep.GTS timestamp_begin current_year_begin
attr Rep.GTS timestamp_end previous_day_end
attr Rep.GTS userExitFn saveGTS
</pre>
'''LogDB''' ist der Name des angeschlossenen DbLog-Devices und ist natürlich entsprechend anzupassen. Unbedingt wichtig ist es, den Start der Auswertung auf den Beginn des Jahres zu setzen. Das Attribut '''timestamp_begin = current_year_begin''' erfüllt diese Bedingung. Das Attribut '''userExitFn''' wird noch erläutert.
Die Berechnung wird gestartet mit dem Befehl:
:<code>set Rep.GTS averageValue display</code>
Im Ergebnis entstehen Readings die auszugsweise nachfolgend aufgelistet sind:
<pre>
    2021-02-26 22:32:08  2021-02-11__MyWetter__temperature__AVGDMGWS__2021-02-11 -5.1
    2021-02-26 22:32:08  2021-02-11__MyWetter__temperature__GrasslandTemperatureSum 40.1
    2021-02-26 22:32:08  2021-02-12__MyWetter__temperature__AVGDMGWS__2021-02-12 -6.8
    2021-02-26 22:32:08  2021-02-12__MyWetter__temperature__GrasslandTemperatureSum 40.1
    2021-02-26 22:32:08  2021-02-13__MyWetter__temperature__AVGDMGWS__2021-02-13 insufficient values - execute get Rep.GTS versionNotes 2 for further information
    2021-02-26 22:32:08  2021-02-14__MyWetter__temperature__AVGDMGWS__2021-02-14 -8.2
    2021-02-26 22:32:08  2021-02-14__MyWetter__temperature__GrasslandTemperatureSum 40.1
    2021-02-26 22:32:08  2021-02-15__MyWetter__temperature__AVGDMGWS__2021-02-15 -3.2
    2021-02-26 22:32:08  2021-02-15__MyWetter__temperature__GrasslandTemperatureSum 40.1
</pre>
Die Meldung "insufficient values - execute get Rep.GTS versionNotes 2 for further information" ist ein Hinweis darauf, dass die für die Ermittlung der Durchschnittstagestemperaturen nach den Regularien des Deutschen Wettederdienstes geforderten Werte nicht (komplett) in der DB vorhanden sind.
Um die Grünlandtemperatursumme im SVG-Diagramm anzuzeigen, muss dieser Wert für jeden Tag in der DB gespeichert werden. Alle benötigten Informationen sind in den DbRep-Readings, z.B.:
<pre>
2021-02-12__MyWetter__temperature__GrasslandTemperatureSum 40.1
</pre>
vorhanden. Der Präfix enthält das Datum, für das die jeweilige Grünlandtemperatursumme gilt, d.h., es muß nur noch ein neuer Wert zu diesem korrespondierenden Datum in die DB geschrieben werden. Das wird durch eine kleine Routine in 99_myUtils.pm erreicht, die im Attribut '''userExitFn''' des DbRep-Devices hinterlegt wird.
'''Routine in 99_myUtils.pm einfügen:'''
<syntaxhighlight lang="perl">
##############################################################
#              speichern GTS in Datenbank
##############################################################
sub saveGTS {
  my $name    = shift;
  my $reading = shift;
  my $value  = shift;
  my $device  = "MyWetter";  # Weather-Device -> anpassen !
  my $logdev  = "LogDB";    # DbLOg Device  -> anpassen !
 
  # 2021-02-14__MyWetter__temperature__GrasslandTemperatureSum 40.1
  if($reading =~ /GrasslandTemperatureSum/x) {
      my ($date)    = (split "__", $reading)[0];
  my ($y,$m,$d) = split "-", $date;
  my $ts        = "$y-$m-$d 12:00:00";
 
      CommandSet(undef, "$logdev addCacheLine $ts|$device|calculated|calculated|GrasslandTemperatureSum|$value|"); 
  }
return;
}
</syntaxhighlight>
In der Routine sind die Variablen '''$device''' und '''$logdev''' mit den realen Devices Weather und DbLog zu ersetzen.
;Funktionsweise
:Ist der Auswertungslauf beendet, werden alle Readings, die "GrasslandTemperatureSum" enthalten, in ihre Bestandteile aufgesplittet, der Timestamp zum Speichern in der Datenbank formatiert und der neu in der DB zu erstellende Datensatz in den Cache des DbLog-Devices eingefügt. Der nächste Sync-Lauf in DbLog fügt die im Cache erstellten Datensätze in die Datenbank ein.
==== Definition des SVG-Devices ====
[[Datei:gts2.PNG|right|thumb|400px|GTS SVG Darstellung]]
In der Datenbank befinden sich nun Datensätze mit dem '''Device MyWetter''' und dem '''Reading GrasslandTemperatureSum''' die im SVG Diagramm ausgewertet werden können.
RAW-Definition des SVG-Devices:
<pre>
defmod SVG_GTS SVG LogDB:SVG_GTS:HISTORY
attr SVG_GTS fixedrange month
attr SVG_GTS label "aktuelle Grünlandtemperatur $data{max1}"
attr SVG_GTS room SVG
</pre>
Die dazugehörige GPLOT-Datei (SVG_GTS.gplot):
<pre>
# Created by FHEM/98_SVG.pm, 2021-02-27 08:08:30
set terminal png transparent size <SIZE> crop
set output '<OUT>.png'
set xdata time
set timefmt "%Y-%m-%d_%H:%M:%S"
set xlabel " "
set title '<L1>'
set ytics
set y2tics
set grid
set ylabel "Score"
set y2label "Score"
#LogDB MyWetter:GrasslandTemperatureSum::
plot "<IN>" using 1:2 axes x1y2 title 'GTS' ls l1fill lw 1 with ibars
</pre>
[[Kategorie:Logging]]

Aktuelle Version vom 16. Februar 2024, 21:59 Uhr

DbRep
Zweck / Funktion
Reporting und Management von DbLog-Datenbankinhalten
Allgemein
Typ Hilfsmodul
Details
Dokumentation EN / DE
Support (Forum) Automatisierung
Modulname 93_DbRep.pm
Ersteller DS_Starter (Forum /Wiki)
Wichtig: sofern vorhanden, gilt im Zweifel immer die (englische) Beschreibung in der commandref!


Zweck und Einsatz des Moduls

Zweck des Moduls ist es, den Inhalt von DbLog-Datenbanken nach bestimmten Kriterien zu durchsuchen, zu managen, das Ergebnis hinsichtlich verschiedener Aggregationen auszuwerten und als Readings darzustellen. Die Abgrenzung der zu berücksichtigenden Datenbankinhalte erfolgt durch die Angabe von Device, Reading und die Zeitgrenzen für Auswertungsbeginn bzw. Auswertungsende.

Alle Datenbankoperationen werden nichtblockierend ausgeführt. Die Ausführungszeit der (SQL)-Hintergrundoperationen kann optional ebenfalls als Reading bereitgestellt werden (siehe Attribute). Alle vorhandenen Readings werden vor einer neuen Operation gelöscht. Durch das Attribut "readingPreventFromDel" kann eine Komma separierte Liste von Readings angegeben werden die nicht gelöscht werden sollen.

Zur Zeit ist das Modul durch folgende Leistungsmerkmale gekennzeichnet:

  • Selektion und Anzeige aller Datensätze innerhalb einstellbarer Zeitgrenzen.
  • Darstellung der Datensätze einer Device/Reading-Kombination innerhalb einstellbarer Zeitgrenzen.
  • Selektion der Datensätze unter Verwendung von dynamisch berechneter Zeitgrenzen zum Ausführungszeitpunkt.
  • Dubletten-Hervorhebung bei Datensatzanzeige (fetchrows)
  • Berechnung der Anzahl von Datensätzen einer Device/Reading-Kombination unter Berücksichtigung von Zeitgrenzen und verschiedenen Aggregationen.
  • Die Berechnung von Summen-, Differenz-, Maximum-, Minimum- und Durchschnittswerten numerischer Readings in Zeitgrenzen und verschiedenen Aggregationen.
  • Speichern von Summen-, Differenz- , Maximum- , Minimum- und Durchschnittswertberechnungen in der Datenbank
  • Löschung von Datensätzen. Die Eingrenzung der Löschung kann durch Device und/oder Reading sowie fixer oder dynamisch berechneter Zeitgrenzen zum Ausführungszeitpunkt erfolgen.
  • Export von Datensätzen in ein File im CSV-Format.
  • Import von Datensätzen aus File im CSV-Format.
  • Umbenennen von Device/Readings in Datenbanksätzen
  • Änderung von gespeicherten Reading-Werten (VALUES) in der Datenbank (changeValue)
  • automatisches Umbenennen (Autorename) von Device-Namen in Datenbanksätzen und DbRep-Definitionen nach FHEM "rename" Befehl (siehe DbRep-Agent)
  • Ausführen von beliebigen benutzerspezifischen SQL-Kommandos
  • Backups der FHEM-Datenbank im laufenden Betrieb erstellen (MySQL, SQLite) mit/ohne Komprimierung der Dumpfiles
  • senden und versionieren Dumpfiles nach dem Backup an einen FTP-Server
  • Restore von SQLite-Dumps und MySQL serverSide-Backups
  • Optimierung der angeschlossenen Datenbank (optimizeTables, vacuum)
  • Ausgabe der existierenden Datenbankprozesse (MySQL)
  • leeren der current-Tabelle
  • Auffüllen der current-Tabelle mit einem (einstellbaren) Extrakt der history-Tabelle
  • Bereinigung sequentiell aufeinander folgender Datensätze (sequentielle Dublettenbereinigung)
  • Reparatur einer korrupten SQLite Datenbank ("database disk image is malformed")
  • Übertragung von Datensätzen aus der Quelldatenbank in eine andere (Standby) Datenbank (syncStandby)
  • Reduktion der Anzahl von Datensätzen in der Datenbank (reduceLog)
  • Löschen von doppelten Datensätzen (delDoublets)
  • Löschen und (Wieder)anlegen der für DbLog und DbRep benötigten Indizes (index)

Zur Aktivierung der Funktion "Autorename" wird dem definierten DbRep-Device mit dem Attribut "role" die Rolle "Agent" zugewiesen. Die Standardrolle nach Definition ist "Client". Mehr ist dazu im Abschnitt DbRep-Agent beschrieben.

DbRep stellt dem Nutzer einen UserExit zur Verfügung. Über diese Schnittstelle kann der Nutzer in Abhängigkeit von frei definierbaren Reading/Value-Kombinationen (Regex) eigenen Code zur Ausführung bringen. Diese Schnittstelle arbeitet unabhängig von einer Eventgenerierung. Weitere Informationen dazu ist unter Attribut "userExitFn" beschrieben.

FHEM-Forum: Modul 93_DbRep - Reporting und Management von Datenbankinhalten (DbLog)

Voraussetzungen und Abgrenzungen

Das Modul setzt den Einsatz einer oder mehrerer DBLog-Instanzen voraus (bisher getestet mit PostgreSQL, MySQL und SQLite). Es werden die Zugangsdaten dieser Datenbankdefinition aus der Konfiguration des entsprechenden DbLog-Device genutzt. Es werden nur Inhalte der Tabelle "history" berücksichtigt (Ausnahme Kommando "sqlCmd").

Überblick, welche anderen Perl-Module DbRep verwendet:

POSIX
Time::HiRes
Time::Local
Scalar::Util
DBI
Blocking (FHEM-Modul)
Encode

Definition

 define <name> DbRep <Name der DbLog-instanz>

(*) <Name der DbLog-Instanz>: Es wird der Name der auszuwertenden DbLog-Datenbankdefinition angegeben, NICHT die Datenbank selbst.

Aus Performancegründen sollte der Index "Report_Idx" in der Datenbank (Tabelle history) angelegt sein. Ab der DbRep-Version 8.20.0 kann dieser Index, sowie die für DbLog benötigten Indizes, verwaltet werden. Der Index "Report_Idx" wird einfach mit dem DbRep-Befehl:

 set <name> index recreate_Report_Idx 

auf der Datenbank angelegt. Ist er bereits vorhanden, wird er gelöscht und erneut angelegt.

Set

Siehe commandref/DbRep-set

Get

Siehe commandref/DbRep-get

Attribute

Siehe commandref/DbRep-attr

DbRep Agent - automatisches Ändern von Device-Namen in Datenbanken und DbRep-Definitionen nach FHEM "rename"

Mit dem Attribut "role" wird die Rolle des DbRep-Device festgelegt. Die Standardrolle ist "Client". Mit der Änderung der Rolle in "Agent" wird das Device veranlasst auf Umbenennungen von Geräten in der FHEM Installation zu reagieren.

Durch den DbRep-Agenten werden folgende Features aktiviert wenn ein Gerät in FHEM mit "rename" umbenannt wird:

  • in der dem DbRep-Agenten zugeordneten Datenbank (Internal Database) wird nach Datensätzen mit dem alten Gerätenamen gesucht und dieser Gerätename in allen betroffenen Datensätzen in den neuen Namen geändert.
  • in dem DbRep-Agenten zugeordneten DbLog-Device wird in der Definition das alte durch das umbenannte Device ersetzt. Dadurch erfolgt ein weiteres Logging des umbenannten Device in der Datenbank.
  • in den existierenden DbRep-Definitionen vom Typ "Client" wird ein evtl. gesetztes Attribut "device = alter Devicename" in "device = neuer Devicename" geändert. Dadurch werden Auswertungsdefinitionen bei Geräteumbenennungen automatisch konsistent gehalten.

Mit der Änderung in einen Agenten sind folgende Restriktionen verbunden, die mit dem Setzen des Attributes "role = Agent" eingeschaltet und geprüft werden:

  • es kann nur einen Agenten pro Datenbank in der FHEM-Installation geben. Ist mehr als eine Datenbank mit DbLog definiert, können ebenso viele DbRep-Agenten eingerichtet werden
  • mit der Umwandlung in einen Agenten wird nur noch das Set-Komando "renameDevice" verfügbar sein sowie nur ein eingeschränkter Satz von DbRep-spezifischen Attributen zugelassen. Wird ein DbRep-Device vom bisherigen Typ "Client" in einen Agenten geändert, werden evtl. gesetzte und nun nicht mehr zugelassene Attribute glöscht.

Die Aktivitäten wie Datenbankänderungen bzw. Änderungen an anderen DbRep-Definitionen werden im Logfile mit verbose=3 protokolliert. Damit die renameDevice-Funktion bei großen Datenbanken nicht in ein timeout läuft, sollte das Attribut "timeout" entsprechend dimensioniert werden. Wie alle Datenbankoperationen des Moduls wird auch das Autorename nonblocking ausgeführt.

Beispiel für die Definition eines DbRep-Device als Agent:

define Rep.Agent DbRep LogDB
attr Rep.Agent devStateIcon connected:10px-kreis-gelb .*disconnect:10px-kreis-rot .*done:10px-kreis-gruen
attr Rep.Agent icon security
attr Rep.Agent role Agent
attr Rep.Agent room DbLog
attr Rep.Agent showproctime 1
attr Rep.Agent stateFormat { ReadingsVal("$name","state", undef) eq "running" ? "renaming" : ReadingsVal("$name","state", undef). " » ProcTime: ".ReadingsVal("$name","sql_processing_time", undef)." sec"}
attr Rep.Agent timeout 86400

hilfreiche SQL Statements

In dieser Rubrik werden SQL-Statements zusammengetragen, die User für ihre Auswertungen hilfreich fanden und anderen Anwendern zur Verfügung stellen.

Im Folgenden wird MySQL auch stellvertretend für MariaDB verwendet.

Die Statements können mit dem Befehl:

set <DbRep> sqlCmd <SQL-Statement>

ausgeführt werden. Für die Anpassung der Ergebnisdarstellung ist das Attribut sqlResultFormat verfügbar.

Hinweis:
Die Statements sind durch den DbRep-Modulautor nicht in jedem Fall getestet und dem Anwender obliegt vor Anwendung der Statements eine Datenbanksicherung durchzuführen um im Fehlerfall diese wieder herstellen zu können.


Den ersten und den letzten Wert eines Zeitraums selektieren bzw. deren Differenz (MySQL)

Beispiel 1:

Den ersten und letzten Wert der durch die DB-Variablen bestimmten Parameter ausgegeben.

Verwendete DB-Variablen (@) : begin_time, end_time, device und reading

SET @begin_time='2020-06-02 12:30:00';SET @end_time='2020-06-02 18:00:00';
SET @device='shelly02';SET @reading='energy_0';

SELECT TIMESTAMP,READING,round(VALUE/1000,0) AS VALUE
   FROM (
     (SELECT TIMESTAMP,READING,VALUE FROM history WHERE DEVICE = @device AND READING = @reading AND TIMESTAMP >= @begin_time and TIMESTAMP <= @end_time ORDER BY TIMESTAMP LIMIT 1)
     UNION ALL 
     (SELECT TIMESTAMP,READING,VALUE FROM history WHERE DEVICE = @device AND READING = @reading AND TIMESTAMP >= @begin_time and TIMESTAMP <= @end_time ORDER BY TIMESTAMP DESC LIMIT 1)
  ) AS X1;

+---------------------+----------+-------+
| TIMESTAMP           | READING  | VALUE |
+---------------------+----------+-------+
| 2020-06-02 12:31:01 | energy_0 |    69 |
| 2020-06-02 16:31:05 | energy_0 |    73 |
+---------------------+----------+-------+

Beispiel 2:

Berechnung der Differenz vom 1. Beispiel

Verwendete DB-Variablen (@) : begin_time, end_time, device und reading

Hierbei ist zu beachten, dass die einzelnen Selects vertauscht wurden um den TIMESTAMP des ersten Select zu bekommen.

Die Subtraktion wurde durch "mal Minus Eins" des kleineren Wertes erreicht.

SET @begin_time='2020-06-02 12:30:00';SET @end_time='2020-06-02 18:00:00';
SET @device='shelly02';SET @reading='energy_0';

SELECT TIMESTAMP,READING,round(sum(VALUE)/1000,0) AS VALUE
   FROM (
     (SELECT TIMESTAMP,READING,VALUE FROM history WHERE DEVICE = @device AND READING = @reading AND TIMESTAMP >= @begin_time and TIMESTAMP <= @end_time ORDER BY TIMESTAMP DESC LIMIT 1)
     UNION ALL 
     (SELECT TIMESTAMP,READING,(VALUE * -1) AS VALUE FROM history WHERE DEVICE = @device  AND  READING = @reading AND TIMESTAMP >= @begin_time and TIMESTAMP <= @end_time ORDER BY TIMESTAMP LIMIT 1)
  ) AS X1;

+---------------------+----------+-------+
| TIMESTAMP           | READING  | VALUE |
+---------------------+----------+-------+
| 2020-06-02 16:31:05 | energy_0 |     4 |
+---------------------+----------+-------+

Die Formatierung

 round(sum(VALUE)/1000,0) AS VALUE

sollte eventuell angepasst werden, da diese natürlich zum zu erwartenden Ergebnis passen muss.

Wieviel PV-Leistung wird von einem Starkverbraucher verwendet (MySQL)

Hier wurde versucht aus der Datenbank zu ermitteln, wieviel PV-Leistung für z.B. eine Wärmepumpe übrig bleibt. Dabei wurde der grundlegenden Hausverbrauch als erstes in Abzug gebracht und der Rest mit dem Starkverbraucher verrechnet. Natürlich kann man solch einen Report nur für einen Verbraucher verwenden, da man ja den Überschuss nur einem zuordnen kann. Auch wird hier mit einem Stundendurchschnitt gerechnet, was somit als Näherung angesehen werden muss.

1) Zuerst werden die einzelnen Daten für einen Tag zusammen gesucht
2) Jeder Datenteil wird mit einem Durchschnitt auf Stundenbasis berechnet
3) Im JOIN werden alle Datenteile für die relevanten Stunden zusammen geführt. Maßgeblich hierbei ist der Betrieb des Starkverbrauchers
   zu dem Zeitpunkt wo auch PV-Leistung verfügbar war. Alle anderen Stunden tauchen danach nicht mehr auf. Ist es ein Hybrid WR mit
   Speicher, dann kann auch in der Nacht eine Unterstützung erfolgen, was im Winter jedoch recht selten sein wird.
4) Es wird der restliche Hausverbrauch ermittelt. Achtung, der Starkverbraucher ist ein Verbraucher, weshalb die Leistung im Zähler
   negativ summiert wird. Ein "+consumer_P" ist somit eine Summe mit einem negativen Wert ;-)
5) Im letzten SELECT werden dann die Daten final aufbereitet und formatiert, auch der prozentuale Anteil der PV-Leistung am gesamten
   Starkverbrauch wird noch berechnet.
consumer          = Das Zähler Device des Starkverbrauchers
consumer_P        = negative Leistungsanzeige in Watt, da es ein Verbraucher ist!
generator         = Das Wechselrichter Device
generator_P       = Die AC Ausgangsleistung in Watt
Home_consumtion_P = Der Hausverbrauch inklusive des Starkverbrauchers in Watt (ebenfalls im generator Device)

Die verwendeten Variablen lassen sich im DbRep als Attribut setzen

sqlCmdVars	
SET @date:='2022-12-07', @consumer:='StromZaehler_Heizung', @consumer_P:='SMAEM1901401955_Saldo_Wirkleistung', @generator:='WR_1', @generator_P:='SW_Total_AC_Active_P', @Home_Consumtion_P:='SW_Home_own_consumption';

Hier nun das SELECT Statement

 SET @date:='2022-12-07',
     @consumer:='StromZaehler_Heizung', @consumer_P:='SMAEM1901401955_Saldo_Wirkleistung',
     @generator:='WR_1', @generator_P:='SW_Total_AC_Active_P', @Home_Consumtion_P:='SW_Home_own_consumption';

 SELECT
   X.HOUR,
   cast(X.generator_P AS decimal(7,2)) AS generator_P,
   cast(X.Home_Consumption AS decimal(7,2)) AS Home_Consumption,
   cast(X.PV_after_Home_Consumtion AS decimal(7,2)) AS PV_after_Home_Consumtion,
   cast(X.consumer_P AS decimal(7,2)) AS consumer_P,
   if(consumer_P+PV_after_Home_Consumtion <= 0,cast(round(abs(consumer_P+PV_after_Home_Consumtion),2) AS decimal(7,2)),0) AS from_Grid,
   if(round(consumer_P+PV_after_Home_Consumtion,2) <= 0,round(abs(PV_after_Home_Consumtion*100/consumer_P),0),100) AS Percent
 FROM (
   SELECT
     X1.HOUR,
     generator_P,
     round(Home_Consumtion_P+consumer_P,2) AS Home_Consumption,
     if(Home_Consumtion_P+consumer_P-generator_P < 0,round(abs(Home_Consumtion_P+consumer_P-generator_P),2),0) AS PV_after_Home_Consumtion,
     consumer_P
   FROM (
     SELECT
       hour(TIMESTAMP) AS HOUR,
       round(avg(value),2) AS consumer_P
     FROM history
     WHERE
       TIMESTAMP > @date and TIMESTAMP < DATE_ADD(@date,INTERVAL 1 DAY) AND
       DEVICE = @Consumer AND
       READING = @consumer_P
     GROUP BY 1
     ) X1
   JOIN (
     SELECT
       hour(TIMESTAMP) AS HOUR,
       round(avg(value),2) AS generator_P
     FROM history
     WHERE
       TIMESTAMP > @date AND TIMESTAMP < DATE_ADD(@date,INTERVAL 1 DAY) AND
       DEVICE    = @generator AND
       READING   = @generator_P AND
       VALUE     > 10
     GROUP BY 1
     ) X2
   JOIN (
     SELECT
       hour(TIMESTAMP) AS HOUR,
       round(avg(value),2) AS Home_Consumtion_P
     FROM history
     WHERE
       TIMESTAMP > @date and TIMESTAMP < DATE_ADD(@date,INTERVAL 1 DAY) AND
       DEVICE  = @generator AND
       READING = @Home_Consumtion_P AND
       VALUE   > 10
     GROUP BY 1
     ) X3
   ON    X1.HOUR = X2.HOUR
     AND X1.HOUR = X3.HOUR
   ) X;

Temperaturdifferenz eines Pufferspeichers über die Zeit ermitteln (MySQL)

Beispiel 1:

Temperaturdifferenz des Pufferspeichers über die Zeit in Minuten

Referenz zum DBRep : set [device] sqlSpecial readingsDifferenceByTimeDelta

Verwendete DB-Variablen (@) : device und reading, diff und delta werden für Berechnungen benötigt und sind bei erneutem Aufruf zurück zu setzen

Anmerkungen:

- Der Zeitraum wird in diesem Beispiel als die letzten 24 Stunden festgelegt

- Die Zeitdifferenz für die Änderung steht in der Spalte DELTA in Minuten

- In FHEM wurde mit event-on-change-reading ins DBLog geschrieben

- Bei dieser Ausgabe kann man erkennen, dass die Temperaturänderung im Wärmespeicher oft sehr klein ist, was ja auch so gewollt ist

- Um 14:00 Uhr beginnt die Wärmepumpe das Warmwasser wieder aufzuheizen

SET @device='Heizung';SET @reading='hotWaterTemperature';
SET @diff=0;SET @delta=NULL;

SELECT t1.TIMESTAMP,t1.READING,t1.VALUE,t1.DIFF,t1.DELTA
  FROM
    (
SELECT TIMESTAMP,READING,VALUE,
       if(@diff = 0,NULL, cast((VALUE-@diff) AS DECIMAL(3,1))) AS DIFF,
       @diff:=VALUE                                            AS curr_V,
       TIMESTAMPDIFF(MINUTE,@delta,TIMESTAMP)                  AS DELTA,
       @delta:=TIMESTAMP                                       AS curr_T
  FROM  history
  WHERE DEVICE     = @device  AND
        READING    = @reading AND
        TIMESTAMP >= NOW() - INTERVAL 1 DAY
  ORDER BY TIMESTAMP
    ) t1;
+---------------------+---------------------+-------+------+-------+
| TIMESTAMP           | READING             | VALUE | DIFF | DELTA |
+---------------------+---------------------+-------+------+-------+
| 2020-06-04 10:05:20 | hotWaterTemperature | 46.5  | NULL |  NULL |
| 2020-06-04 10:35:31 | hotWaterTemperature | 46.4  | -0.1 |    30 |
| 2020-06-04 11:00:31 | hotWaterTemperature | 46.2  | -0.2 |    25 |
snip...
| 2020-06-04 13:50:42 | hotWaterTemperature | 44.5  | -1.0 |     5 |
| 2020-06-04 13:55:42 | hotWaterTemperature | 44.0  | -0.5 |     5 |
| 2020-06-04 14:00:42 | hotWaterTemperature | 43.9  | -0.1 |     5 |
| 2020-06-04 14:10:42 | hotWaterTemperature | 41.7  | -1.8 |     5 |
snip...
| 2020-06-04 14:51:05 | hotWaterTemperature | 51.3  |  0.1 |     5 |
| 2020-06-04 17:01:15 | hotWaterTemperature | 51.2  | -0.1 |   130 |
snip...
| 2020-06-04 22:10:30 | hotWaterTemperature | 50.5  | -0.2 |     5 |
snip...
| 2020-06-05 11:30:45 | hotWaterTemperature | 46.1  | -0.1 |    25 |
+---------------------+---------------------+-------+------+-------+

Beispiel 2:

Summieren der Temperaturdifferenz auf Stundenbasis

Verwendete DB-Variablen (@) : device und reading, diff und delta werden für Berechnungen benötigt und sind bei erneutem Aufruf zurück zu setzen

Anmerkungen:

- Der Zeitraum wird in diesem Beispiel als die letzten 24 Stunden festgelegt

- Die Zeitdifferenz wird bei diesem Beispiel nicht ausgegeben

- Alle Differenzen nach der Vollen Stunde werden der nächsten Stunde zugeschlagen

- In FHEM wurde mit event-on-change-reading ins DBLog geschrieben

- Um 14:00 Uhr beginnt die Wärmepumpe das Warmwasser wieder aufzuheizen

- Bei diesem Wärmespeicher sind Temperaturschwankungen im Bereich von Null Komma irgendwas, als Messwert recht ungenau, was im Beispiel 1 zu der Vielzahl an Messwerten geführt hat. Durch Beispiel 2 auf Stundenbasis lässt sich bereits mehr erkennen. Hier wäre der Wärmepumpeneinsatz um 14:00 Uhr mit dem Pumpenvorlauf (-1.8) und der Lauf der Zirkulationspumpe um 8:00 und 8:30 Uhr zu sehen, was den Wärmespeicher um 1,2 Grad abgekühlt hat.

SET @device='Heizung';SET @reading='hotWaterTemperature';
SET @diff=0;SET @delta=NULL;

SELECT xTIMESTAMP AS TIMESTAMP,READING,xVALUE AS VALUE,xDIFF AS DIFF
  FROM
    (SELECT DATE_FORMAT(DATE_ADD(t1.TIMESTAMP,INTERVAL (IF(MINUTE(t1.TIMESTAMP) > 0, 60, 0)-MINUTE(t1.TIMESTAMP)) MINUTE),'%Y-%m-%d %H:00:00') AS xTIMESTAMP,
            t1.READING,
            round(t1.VALUE) AS xVALUE,
            sum(t1.DIFF)    AS xDIFF,
            sum(t1.DELTA)   AS xDELTA
       FROM
         (SELECT TIMESTAMP,READING,VALUE,
                 if(@diff = 0,NULL, cast((VALUE-@diff) AS DECIMAL(3,1))) AS DIFF,
                 @diff:=VALUE                                            AS curr_V,
                 TIMESTAMPDIFF(MINUTE,@delta,TIMESTAMP)                  AS DELTA,
                 @delta:=TIMESTAMP                                       AS curr_T
            FROM  history
            WHERE DEVICE     = @device  AND
                  READING    = @reading AND
                  TIMESTAMP >= NOW() - INTERVAL 1 DAY
            ORDER BY TIMESTAMP
         ) t1
       GROUP BY xTIMESTAMP WITH ROLLUP) x1

   WHERE xDELTA     IS NOT NULL AND
         xTIMESTAMP IS NOT NULL;
+---------------------+---------------------+-------+------+
| TIMESTAMP           | READING             | VALUE | DIFF |
+---------------------+---------------------+-------+------+
| 2020-06-04 12:00:00 | hotWaterTemperature |    46 | -0.1 |
| 2020-06-04 13:00:00 | hotWaterTemperature |    46 | -0.4 |
| 2020-06-04 14:00:00 | hotWaterTemperature |    44 | -1.8 |
| 2020-06-04 15:00:00 | hotWaterTemperature |    51 |  7.4 |
| 2020-06-04 18:00:00 | hotWaterTemperature |    51 | -0.1 |
snip...
| 2020-06-05 07:00:00 | hotWaterTemperature |    48 | -0.3 |
| 2020-06-05 08:00:00 | hotWaterTemperature |    48 | -0.2 |
| 2020-06-05 09:00:00 | hotWaterTemperature |    48 | -1.2 |
| 2020-06-05 10:00:00 | hotWaterTemperature |    47 | -0.3 |
snip...
+---------------------+---------------------+-------+------+

Alle aktuellsten readings eines Device im letzten Tagesverlauf (MySQL)

Beispiel :

Verwendete DB-Variablen (@) : device

Anmerkungen:

- Der Zeitraum wird in diesem Beispiel als die letzten 24 Stunden festgelegt

- In FHEM wurde mit event-on-change-reading ins DBLog geschrieben, wodurch nicht alle readings immer zeitlich in der DB aufeinander folgen oder einige im Zeitraum mehrfach geschrieben wurden. Mit diesem Aufruf erscheinen immer die letzten Aktualisierungen.

- Sollten einige readings nicht erscheinen, so wurden diese vor dem Zeitraum letztmalig aktualisiert. Mit "INTERVAL [n] DAY könnten diese eventuell auch noch gefunden werden. Bitte hier die Laufzeit beachten!

SET @device = 'Heizung';

SELECT t1.TIMESTAMP,t1.DEVICE,t1.READING,t1.VALUE
  FROM history t1
  INNER JOIN
   (select max(TIMESTAMP) AS TIMESTAMP,DEVICE,READING
      from history
      where DEVICE    = @device and
            TIMESTAMP > NOW() - INTERVAL 1 DAY
      group by READING) x
  ON x.TIMESTAMP = t1.TIMESTAMP AND
     x.DEVICE    = t1.DEVICE    AND
     x.READING   = t1.READING;


Device / Reading Daten in eine CSV-Datei exportieren (MySQL)

Grundsätzlich gibt es für diesen Zweck das eingebaute Set-Kommando exportToFile.

Eine weitere Möglichkeit ist die Verwendung von spezifischen Datenbankkommandos, die mit Set sqlCmd ausgeführt werden können.

Im Beispiel wird ein CSV File geschrieben, welches die Daten des Devices 'SMA_Energymeter' enthält. Wichtig ist, nicht zur Trennung von SQL-Befehlen dienende Semikolons durch Verdopplung zu escapen (z.B. im String "...TERMINATED BY ';;'...").

SET @TS = DATE_FORMAT(NOW(),'_%Y_%m_%d');

SET @FOLDER = '/volume1/ApplicationBackup/';
SET @PREFIX = 'export';
SET @EXT    = '.csv';

SET @CMD = CONCAT("	SELECT *
			FROM `fhemtest`.`history`
			WHERE `DEVICE`='SMA_Energymeter' AND TIMESTAMP > DATE_SUB(CURRENT_DATE(),INTERVAL 1 DAY)
			INTO OUTFILE '",@FOLDER,@PREFIX,@TS,@EXT,"'
			FIELDS ENCLOSED BY '\"'
			TERMINATED BY ';;'
			ESCAPED BY '\"'"," 
			LINES TERMINATED BY '\r\n';;
		");

PREPARE statement FROM @CMD;

EXECUTE statement;


Hinweis: der verwendete Datenbank-User benötigt das FILE Recht.


Gewichtete Mittelwerte von Zeitreihen (MySQL)

Zeitreihen in FHEM sind Messwerte (z.B. Temperatur, Stromverbrauch, PV-Produktion ...), die i.A. nicht zu äquidistanten Zeitpunkten ermittelt und protokolliert werden, sondern nur bei Änderung der Messgröße. Für eine Zusammenfassung ("Aggregation") zum Zwecke statistischer Auswertung oder einfach zur Datenreduktion sollte daher bei einer Mittelwertbildung auf zeitlich gewichtete Mittelung zurückgegriffen werden. Ein interessierender Gesamtzeitraum wird dazu in einzelne Mittelungsintervalle (sogen. bins) aufgeteilt, für die dann der gewichtete Mittelwert berechnet wird.

Die (non-blocking) DbRep-Funktion 'set averageValue' macht, in Verbindung mit den Attributen 'aggregation' (Auswahl einer Aggregationsperiode) und 'averageCalcForm'='avgTimeWeightMean' genau das. Dabei hat man als bin-Länge 'minute', 'hour', 'day', 'week', 'month' und 'year' zur Verfügung.

Der folgende SQL-Query 1 kennt als bin-Längen auch Viertelstunden ('qhour'), Vierteltage ('qday') und Quartal ('qyear'). Die Parameter @sortformat=["minute"|"qhour"|"hour"|"qday"|"day"|"week"|"month"|"qyear"|"year"] und @weighted=["yes"|"no"] müssen vor Ausführung des Codes mit dem Attribut sqlCmdVars gesetzt werden, z.B.

set <Your_DbRep_device> sqlCmdVars SET @sortformat="hour", @weighted="yes", @count=<n>;
SQL-Query 1 ein/ausblenden
SELECT
  avgtim, 
  CASE @weighted 
    WHEN "yes" THEN 
      CAST((SUM(val * weight)/SUM(weight)) AS DECIMAL(12,4)) 
    ELSE 
      CAST(sum(val) / count(val) AS DECIMAL(12,4)) 
  END AS avrg
FROM (
  SELECT  
    tim,
    avgtim,
    CASE @sortformat 
      WHEN "week" THEN 
        date_format(tim, "%Y-%u 00:00:00")
      ELSE 
        avgtim 
    END AS grouptim,
    CASE
      WHEN avgtim!=nextavgtim THEN 
        to_seconds(nextavgtim)-to_seconds(tim)
      WHEN avgtim!=preavgtim THEN 
        to_seconds(nexttim)-to_seconds(avgtim)
      ELSE 
        to_seconds(nexttim)-to_seconds(tim) 
    END AS weight,  
    CASE
      WHEN avgtim!=preavgtim THEN 
        CASE @weighted WHEN "yes"
          THEN
            CASE 
              WHEN avgtim!=nextavgtim THEN 
                (preval*(to_seconds(tim)-to_seconds(avgtim)) + val*(to_seconds(nextavgtim)-to_seconds(tim)))/ 
                  (to_seconds(nextavgtim)-to_seconds(avgtim))
              ELSE 
                (preval*(to_seconds(tim)-to_seconds(avgtim)) + val*(to_seconds(nexttim)-to_seconds(tim)))/
                  (to_seconds(nexttim)-to_seconds(avgtim))
            END
          ELSE 
            val
        END
      ELSE 
        val
    END AS val
  FROM ( 
    SELECT 
      tim, 
      nexttim, 
      val, 
      preval, 
      avgtim,
      LAG(avgtim) OVER (ORDER BY tim) AS preavgtim,
      LEAD(avgtim) OVER (ORDER BY tim) AS nextavgtim
    FROM ( 
      SELECT 
        timestamp AS tim, 
        LEAD(timestamp) OVER (ORDER BY timestamp) AS nexttim, 
        value AS val, 
        LAG(value) OVER (ORDER BY timestamp) AS preval,
        CASE @sortformat
          WHEN "minute" THEN 
            date_format(timestamp, "%Y-%m-%d %H:%i:00") 
          WHEN "qhour"  THEN 
            concat(date_format(timestamp, "%Y-%m-%d %H:"),LPAD(15*(date_format(timestamp,"%i") div 15),2,"0"),":00") 
          WHEN "hour" THEN 
            date_format(timestamp, "%Y-%m-%d %H:00:00") 
          WHEN "qday" THEN 
            concat(date_format(timestamp, "%Y-%m-%d "),LPAD(6*(date_format(timestamp, "%H") div 6),2,"0"),":00:00")
          WHEN "day" THEN 
            date_format(timestamp, "%Y-%m-%d 00:00:00") 
          WHEN "week" THEN 
            date_format(timestamp, "%Y-%m-%d 00:00:00") 
          WHEN "month" THEN 
            date_format(timestamp, "%Y-%m-01 00:00:00") 
          WHEN "qyear" THEN 
            concat(date_format(timestamp, "%Y-"),LPAD(3*(date_format(timestamp, "%m") div 3),2,"0"),"-01 00:00:00")
          ELSE 
            date_format(timestamp, "%Y-01-01 00:00:00")
        END AS avgtim      
      FROM 
        history WHERE TIMESTAMP BETWEEN §timestamp_begin§ AND §timestamp_end§ AND §device§ AND §reading§ ORDER BY timestamp 
    ) select3 
  ) select2
) select1 GROUP BY grouptim

Die Auswahl von device, reading und Zeitraum erfolgt über die üblichen Attribute. Es darf nur ein device und ein reading gewählt werden. Für den Zeitraum btte nur timestamp_begin und timestamp_end nutzen.

Will man keine festen Zeitraster, kann man den Gesamtzeitraum auch einfach in eine Anzahl count von bins aufteilen. Dafür steht der Parameter @count im obigen sqlCmdVars statement (für Query 1 hat er keine Bedeutung).

SQL-Query 2 ein/ausblenden
SELECT 
  tim, 
  CASE 
    WHEN @weighted="yes" THEN 
      CAST(sum(val * (to_seconds(nexttim)-to_seconds(tim))) / sum((to_seconds(nexttim)-to_seconds(tim))) AS DECIMAL(12,4))
    ELSE CAST(sum(val) / count(val) AS DECIMAL(12,4)) 
  END AS avrg
FROM ( 
  SELECT 
    timestamp as tim, 
    value as val, 
    LEAD(timestamp) over (order by timestamp) nexttim, 
    truncate(@count * (to_seconds(timestamp)-to_seconds(§timestamp_begin§)) / (to_seconds(§timestamp_end§)-to_seconds(§timestamp_begin§)),0)/@count as avgtim 
  FROM 
    history WHERE TIMESTAMP BETWEEN §timestamp_begin§ AND §timestamp_end§ AND §device§ AND §reading§  
) select1 group by avgtim

Zur Ausführung der Queries den Code entweder ins Eingabefeld hinter set sqlCmd (bzw. sqlCmdBlocking) kopieren und dann mit set ausführen, oder per set <Your_DbRep_device> sqlCmd <kopierter Code> in der FHEM Kommandozeile ausführen.

Getestet wurden diese MySQL Beispiele mit MariaDB 10.3

Siehe auch im Forum: https://forum.fhem.de/index.php/topic,53584.msg1254036.html#msg1254036


Praxisbeispiele / Hinweise und Lösungsansätze für verschiedene Aufgaben

Definieren eines DbRep-Devices

DbRep initialized.PNG

Das DbRep-Device wird bei der Definition mit der DbLog-Instanz verbunden, in deren angeschlossener Datenbank später die Auswertungen und Operationen stattfinden sollen. Es ist also nicht die Datenbank selbst, sondern das vorher definierte DbLog-Device anzugeben. Die Definition erfolgt z.B. durch:

define Rep.Energy DbRep LogDB       #LogDB ist das zu verbindende DbLog-Device

Bei der Definition werden die Zugangsdaten aus der DbLog-Instanz gelesen und das DbRep-Device mit der Datenbank verbunden. Nach der Definition ist der Status "initialized". Die Verbindung zur Datenbank wird mit der ersten abzuarbeitenden Aufgabe hergestellt. Das Verhalten kann mit dem Attribut fastStart beeinflusst werden. Zu welcher Datanbank das DbRep-Device sich verbunden hat, zeigt das Internal DATABASE.

Damit ist das DbRep-Device grundsätzlich einsatzbereit, aber noch nicht praxistauglich. Werden keine weiteren Eingrenzungen angegeben, kann mit dem so definierten Device mit dem Befehl

set Rep.Energy countEntries

die gesamte Anzahl der Datensätze in der Datenbank ermittelt werden. Für eine weitere Verwendung sind weitere Attribute zu setzen. Um die Funktionen von "Rep.Energy" nur auf z.B. Datensätze in der Datenbank anzuwenden die "STP_5000" im Feld "DEVICE" enthalten, wird das Attribut:

attr Rep.Energy device STP_5000

gesetzt. Weitere Begrenzungen der gerätespezifischen Selektion erfolgt durch das Attribut "reading". So wird durch

attr Rep.Energy reading etotal
Rep configured.PNG

festgelegt, dass sich die (allermeisten) Operationen auf die Kombination aus dem "device STP_5000" und dem "reading etotal" beziehen. Eine zeitliche Eingrenzung der Ergebnisse erfolgt durch die Attribute "timeDiffToNow", "timeOlderThan", "timestamp_begin", "timestamp_end". In dem Beispiel sollen sich die Selektionsergebnisse immer auf die letzten 120 Minuten beziehen. Dazu wird das Attribut

attr Rep.Energy timeDiffToNow 7200    #Der Wert für timeDiffToNow ist in Sekunden anzugeben

gesetzt. Es wird dynamisch bei jeder Operation "<Selektionsbeginn> = <aktuelle Zeit> - 3600s" berechnet und die Datensätze bis zu <aktuelle Zeit> berücksichtigt. Die gesamten Datenbankoperationen und teilweise auch Auswertungen von Selektionen erfolgt mit Blockingcall im Hintergrund. Der Timeout für die Operationen ist per Default auf 86400 Sekunden gesetzt. Über das Attribut "timeout" kann es den eigenen Bedingungen angepasst werden. In dem Beispiel wird timeout auf 300s geändert um auch bei sehr großen Selektionen und Auswertungen nicht in einen timeout zu laufen.

attr Rep.Energy timeout 300

Um auch die Verarbeitungszeiten im Hintergrund als Reading anzeigen zu lassen wird mit mit dem Attribut

attr Rep.Energy showproctime 1

erreicht. Mit diesen Einstellungen ist das Device für den konfiguriert und man kann sich zum Beispiel mit

set Rep.Energy fetchrows

die Datensätze mit der Gesamtenergierzeugung "etotal" des Wechselrichters "STP_5000" die in den letzten 2 Stunden in die DB geschrieben wurden. Nachdem der Befehl in der Gerätedetailsicht ausgeführt wurde, wechselt der state im DeviceOverview auf "running". Sobald im DeviceOverview "done" angezeigt wird sieht man die Ergebnisse nach einem Browserrefresh. Bei der Ausführung einer erneuten Operation werden alle Readings gelöscht. Sollen bestimmte Readings davon ausgenommen werden, kann dem Attribut eine kommaseparierte Liste von zu schützenden Readings übergeben werden.


Allgemein wird empfohlen sich für jede Aufgabe eine separates DbRep-Device anzulegen und entsprechend zu konfigurieren anstatt die Einstellungen ständig den neuen Aufgaben anzupassen. Um den Prozess zu vereinfachen, kann das einmal angelegte Device für eine neue Selektionsaufgabe (zum Beispiel die Datensätze eines SMA Energymeters anzuzeigen bzw. auszuwerten) auf ein neues DbRep-Device kopiert

copy Rep.Energy Rep.SMAMeter

und entsprechend angepasst werden.

Hinweis zur Eventgenerierung:
Je nach genutzter Funktion können sehr viele Readings als Ergebnis generiert werden. Zum Beispiel können mit "fetchrows", wobei jedes Reading einer selektierte Zeile aus der Datenbank entspricht, durchaus mehrere hundert Readings entstehen. Sollte man nicht durch die Attribute "event-on-update-reading" bzw. "event-on-change-reading" die Eventerzeugung auf nur relevante Readings begrenzt haben, können in diesem Fall ebenso viele Events enstehen die das System entsprechend belasten können.
Deswegen wird empfohlen die Eventerzeugung sofort auf z.B. "state" zu begrenzen.



Ermittlung des monatlichen und durchschnittlichen Gasverbrauches mit Vaillant und eBusd MQTT

Es soll der monatliche Gasverbrauch in m3, kWh und Euro sowie der Durchnschnitt in Euro über die gewählten Monate ermittelt werden.

Die auswertenden Daten liegen als "Tics" vor. Es ist eine stetig steigende Zahl ohne Einheit. Sie wird als Reading aus einem Vaillant eBus-MQTT Device geloggt. eBusd liefert PrEnergySumHc2 und PrEnergySumHwc1. Beide Werte werden als Summe im Reading 1_Brenner_Energy_Summe zusammengeführt und geloggt.

Screenshot 2022-12-25 143631.png

Für die Bereitstellung diverser Hilfswerte zur Berechnung existiert ein Dummy-Device VaillantControlDummy.


Dieses Device enthält in User-Attributen folgende Werte:


  • Brennwert_kWh/m3 -> den Brennwert in kWh pro m3 lt. Angaben des Lieferanten
  • Zustandszahl -> die Zustandszahl lt. Angaben des Lieferanten
  • Multiplikator -> ein Faktor zur Umrechnung der geloggten Tics (1_Brenner_Energy_Summe) in m3
  • Tarif -> der persönliche Tarif (Brutto) in €/kWh


Der Multiplikator wurde empirisch als Quotient aus den real verbrauchten m3 und den Tics über einem längeren Zeitraum ermittelt. Das Ergebnis der Multiplikaktion von geloggten Tics und und Multiplikator ergibt die jeweilig verbrauchten m3. Dieser Wert ist Toleranz/Fehler behaftet, hat sich aber in der Praxis als hinreichend genau zur Ermittlung des Gasverbrauchs erwiesen.

Das DbRep Device wird wie weiter oben beschrieben angelegt:

define Rep.gas.allmonths DbRep <DbLog-Devicename>
Screenshot 2022-12-25 143912.png

Die folgenden Attribute werden gesetzt um den auszuwertenden Zeitraum, sowie das auszuwertende Device und Reading in der Datenbank auszuwerten. Die Werte für die Attribute sind natürlich dem persönlichen Umfeld anzupassen:


  • device -> auszuwertendes Device (MQTT2_ebusd_bai)
  • reading -> auszuwertendes Reading (1_Brenner_Energy_Summe)
  • timestamp_begin -> Auswertungszeitraum Beginn (2022-10-01 00:00:00)
  • timestamp_end -> Auswertungszeitraum Ende (current_year_end)
  • aggregation -> month, d.h. der Auswertungszeitraum wird in Monatsscheiben aufgeteilt
  • numDecimalPlaces -> die gwünschte Anzahl der Nachkommastellen im Ergebnis (0)
  • diffAccept -> die akzeptierte Differenz von Diffenzwerten (1000000000), den Wert sehr hoch setzen um keine Datensätze von der Berechnung auszuschließen
  • event-on-update-reading -> state,gas_.*
  • eventMap -> /diffValue display:diffValuedisplay/ (für Verwendung in webCmd)
  • webCmd -> diffValuedisplay


Wird das Device mit diesen Einstellungen gestartet, werden Readings der Form:

2022-10-31_07-05-03__MQTT2_ebusd_bai__1_Brenner_Energy_Summe__DIFF__2022-10
2022-11-30_08-38-18__MQTT2_ebusd_bai__1_Brenner_Energy_Summe__DIFF__2022-11
2022-12-25_12-29-47__MQTT2_ebusd_bai__1_Brenner_Energy_Summe__DIFF__2022-12

generiert. Sie liefern die Anzahl der Tics im jeweiligen Monat.


"Herzstück" zur Ermittlung der Zielwerte ist eine Perl Routine die im Attribut userExitFn hinterlegt wird:

{
  if ($READING =~ /1_Brenner_Energy_Summe__DIFF/ && $VALUE ne '-') {
    my $date      = (split '__', $READING)[0];
    my ($y,$m,$d) = $date =~ /^(\d{4})-(\d{2})-(\d{2})/x;
    
    my $cdd   = "VaillantControlDummy";                            # Steuerungsdummy Device
    my $mpk   = AttrVal($cdd,  'Multiplikator',    '0');
    my $zz    = AttrVal($cdd,  'Zustandszahl',     '0');           # Zustandszahl lt. Lieferant
    my $bw    = AttrVal($cdd,  'Brennwert_kWh/m3', '0');           # Brennwert kWh/m3 lt. Lieferant
    my $tarf  = AttrVal($cdd,  'Tarif',            '0');           # Kosten €/kWh    
    my $ndp   = AttrVal($NAME, 'numDecimalPlaces', '3');
    my $m3    = sprintf "%.${ndp}f", $VALUE/10000 * $mpk;          # verbrauchte m3
    my $kwh   = sprintf "%.${ndp}f", $m3 * $zz * $bw;              # Umrechnung m3 -> kWh
    my $cost  = sprintf "%.${ndp}f", $kwh * $tarf;                 # Kosten = kWh * Tarif
    my $hash  = $defs{$NAME};
    
    readingsBulkUpdate ($hash, $date.'_gas_consumption_m3',         $m3);
    readingsBulkUpdate ($hash, $date.'_gas_consumption_kwh',       $kwh);
    readingsBulkUpdate ($hash, 'gas_cost_euro_'.$y.'-'.$m.'-'.$d, $cost);
  }
  
  if ($READING eq 'state' && $VALUE eq 'done') {
      my $n   = 0;
      my $v   = 0;
      my $avg = 0;
	  
      for my $rdg ( grep { /gas_cost_euro_/x } keys %{$hash->{READINGS}} ) {
          $n++;
          $v += ReadingsNum ($name, $rdg, 0);
      }
	  
      if ($n) {
          my $ndp3 = AttrVal($NAME, 'numDecimalPlaces', '3');
          $avg    = sprintf "%.${ndp3}f", $v / $n;
          readingsBulkUpdate ($hash, 'gas_cost_euro_average', $avg);
      }
  }
}

Sobald ein Reading erstellt wird welches auf den Regex /1_Brenner_Energy_Summe__DIFF/ matcht, werden die User-Attribute aus dem Steuerungsdummy VaillantControlDummy abgerufen und damit die Berechnung der resultierenden m3 ($m3), kWh ($kwh) und Euro ($cost) durchgeführt. Die User-Attribute können natürlich auch im DbRep Device selbst hinterlegt werden. Im vorliegenden Fall wird der Steuerungsdummy noch zu weiteren Aufgabe der Heizungssteuerung verwendet, sodass sich die Verwendung auch für diesen Use Case anbietet.

Es werden die Readings

  • <Datum>_<Zeit>_gas_consumption_m3
  • <Datum>_<Zeit>_gas_consumption_kwh
  • gas_cost_euro_<Datum>

erzeugt.

Hat das Device den Report erfolgreich beendet, wird "state" mit dem Wert "done" erzeugt. Dieser Wert wird ebenfalls in der Routine überwacht und dann der Durchschnittswert ($avg) aus den ermittelten Monatsergebnissen berechnet. Mit dem Ergebnis wird das Reading:

  • gas_cost_euro_average

erstellt.


Zur Gestaltung des Device Overwiew wird im Attribut stateFormat ebenfalls eine Perl Routine hinterlegt:

{
 my $state = ReadingsVal($name, 'state',                    '');
 my $gca   = ReadingsVal($name, 'gas_cost_euro_average', undef);
 
 my $show  = '';
 $show    .= defined $gca ? 'Durchschnittskosten Gas: '.$gca.' €' : 
             $state;
 $show    .= '<br>';
 $show    .= $state =~ /connected/xs   ? FW_makeImage('10px-kreis-gelb')         :
             $state =~ /initialized/xs ? FW_makeImage('control_3dot_hor_s')      :
             $state =~ /disconnect/xs  ? FW_makeImage('10px-kreis-rot')          :
             $state =~ /done/xs        ? FW_makeImage('10px-kreis-gruen')        :
             $state =~ /Warning/xs     ? FW_makeImage('info_warning@darkorange') :
             $state =~ /running/xs     ? ''                                      :
             FW_makeImage('10px-kreis-rot'); 

 "<div>".$show."</div>"
}



Datensätze (Devices) von einer Datenbank in eine andere umziehen (Export/Import)

Zweck

Oft ist es so, dass man zunächst eine DbLog-Datenbank anlegt um alle zu loggenden Events dort aufzuzeichnen. Mit zunehmender Größe bzw. mit dem Wunsch nach einer höheren Strukturierung werden weitere DbLog-Instanzen angelegt. Die bereits geschriebenen Datensätze eines Gerätes müssen nun in eine andere Datenbank verlagert werden, weil das Device nunmehr in dieser Datenbank geloggt und ausgewertet werden soll.

Für das Beispielszenario gilt folgendes:

  • Quelldatenbank aus der das Device entfernt werden soll ist "fhem" mit der DbLog-Instanz "LogDB"
  • Zieldatenbank ist "fhemshort" mit der DbLog-Instanz "LogDBShort"
  • das umzuziehende Device ist "MelderCP1"
  • das definierte DbRep-Device ist "Rep.MelderCP1"

Vorbereitung

Zunächst wird ein DbRep-Device definiert (falls es noch nicht existiert) und für den Verwendungszweck vorbereitet (zur Definition siehe Definieren eines DbRep-Devices).

Das Attribut "reading" bzw. eventuell gesetzte Attribute zur Zeiteingrenzung werden gelöscht, damit alle Datensätze des Devices erfasst werden können. Für den bevorstehenden Export wird mit dem Attribut "expimpFile" der (beschreibbare) Pfad und Dateiname festgelegt, hier im Beispiel ist es "/media/sf_Backup_FHEM/meldercp1.txt".

Anzahl Einträge von MelderCP1

Mit

set Rep.MelderCP1 countEntries

kann man sich nun einen Überblick verschaffen, wie viele Datensätze in der Quelldatenbank für "MelderCP1" enthalten sind und wie viele auch exportiert werden müssen. In dem Beispiel sind es, wie in dem Screenshot zu sehen, 1144 Datensätze.

Falls noch nicht geschehen, wird in der bisherigen DbLog-Definition das zu loggende Device entfernt und in der neuen DbLog-Definition aufgenommen, damit nun keine neuen Datensätze mehr für "MelderCP1" in die bisherige Datenbank geschrieben werden. Hierzu siehe die commandref/DbLog zu DbLog.

Export

Nun werden die Datensätze des Devices "MelderCP1" mit dem folgenden set-Kommando in die Datei "/media/sf_Backup_FHEM/meldercp1.txt" exportiert.

set Rep.MelderCP1 exportToFile

Nach dem Export sollte natürlich im Reading von "Rep.MelderCP1" die gleiche Anzahl von exportierten Datensätzen ausgegeben werden wie vorher mit "countEntries" ermittelt wurde (siehe Screenshot).

Anzahl exportierter Dataensätze

In der Export-Datei sind nun die Datensätze im CVS-Format enthalten.

Ein Ausschnitt:

............
"2016-07-04 17:32:03","MelderCP1","FS20","on-old-for-timer 60","state","on-old-for-timer 60",""
"2016-07-05 07:47:50","MelderCP1","FS20","on-old-for-timer 60","state","on-old-for-timer 60",""
"2016-07-05 08:27:57","MelderCP1","FS20","on-old-for-timer 60","state","on-old-for-timer 60",""
"2016-07-05 08:28:25","MelderCP1","FS20","on-old-for-timer 60","state","on-old-for-timer 60",""
"2016-07-05 10:23:42","MelderCP1","FS20","on-old-for-timer 60","state","on-old-for-timer 60",""
"2016-07-05 10:27:35","MelderCP1","FS20","on-old-for-timer 60","state","on-old-for-timer 60",""
"2016-07-05 17:26:30","MelderCP1","FS20","on-old-for-timer 60","state","on-old-for-timer 60",""
"2016-07-06 07:46:31","MelderCP1","FS20","on-old-for-timer 60","state","on-old-for-timer 60",""
"2016-07-06 11:24:04","MelderCP1","FS20","on-old-for-timer 60","state","on-old-for-timer 60",""
"2016-07-06 11:25:43","MelderCP1","FS20","on-old-for-timer 60","state","on-old-for-timer 60",""
"2016-07-06 11:26:52","MelderCP1","FS20","on-old-for-timer 60","state","on-old-for-timer 60",""
...........

In der bisherigen Datenbank "fhem" können die Datensätze nun gelöscht werden. Dazu versehen wir das DbRep-Device "Rep.MelderCP1" mit dem Attribut "allowDeletion" um die Löschfunktion des Moduls freizuschalten. Nun werden die vorher exportierten Datensätze gelöscht mit:

Anzahl gelöschter Datensätze
set Rep.MelderCP1 delEntries

Auch jetzt ist wieder sicherzustellen dass in der Ausgabe der gelöschten Rows dieselbe Anzahl (1144) der exportierten Datensätze enthalten ist.

Import

Im nächsten Schritt werden die exportierten Daten in die neue Datenbank importiert. Dazu wird in unserem DbRep-Device "Rep.MelderCP1" zunächst das "allowDeletion"-Attribut sicherheitshalber gelöscht. Zur Umstellung des DbRep-Devices auf die neue Datenbank wird die Definition von "Rep.MelderCP1" editiert und dort die neue DbLog-Instanz "LogDBShort" angegeben.

modify Rep.MelderCP1 LogDBShort

Nach einem kurzen Moment wird der state von "Rep.MelderCP1" sich wieder von "initialized" auf "connected" ändern sofern die Umstellung funktioniert hat. Mit einem Vergleichslauf durch

Anzahl Einträge von MelderCP1 in neuer Datenbank nach Umstellung
set Rep.MelderCP1 countEntries

wird ersichtlich, dass sich in dieser Datenbank noch keine (oder vllt. bereits neu geloggte) Einträge von "MelderCP1" befinden.


Nun kann der Import erfolgen. Mit

set Rep.MelderCP1 importFromFile

werden die Daten aus der immer noch im Attribut "expimpFile" hinterlegten Datei "/media/sf_Backup_FHEM/meldercp1.txt" in die Datenbank "fhemshort" importiert. Dieser Vorgang wird als Transaktion ausgeführt. Es werden immer alle Daten oder, im Falle eines Fehlers, KEINE Daten importiert. Damit ist sichergestellt dass der Datenimport immer einen definierten Zustand hat. Sollte der Datenimport sehr umfangreiche Datensätze enthalten und somit schon absehbar sein dass der voreingestellte timeout-Wert von 86400 Sekunden nicht ausreichen wird, kann man vorsorglich das Attribut "timeout" höher setzen, z.B. auf das Doppelte).

Hier in dem Beispiel sind es lediglich 1144 Datensätze die sehr schnell importiert werden. Somit ist keine Anpassung des timeout-Parameters notwendig. Auch nach dem Import wird wieder die Anzahl der verarbeiteten Dataensätze ausgegeben. Sie sollte natürlich ebenfalls mit den vorab ermittelten Zahlen der exportierten Datensätze übereinstimmen.

Anzahl importierter Datensätze

Abschluss

Nach dem erfolgreichen Import sollte das Attribut "expimpFile" wieder entfernt werden damit man nicht versehentlich einen erneuten Import durchführt. Das verwendete Rep.MelderCP1 kann nun wieder mit Zeitbegrenzungsattributen usw. versehen werden um die ursprüngliche Verwendung wieder herzustellen.

Die dargestellte Prozedur stellt einen Komplettumzug eines Devices dar, kann jedoch durch die Angabe der bekannten Attribute auch auf bestimmte Readings in der Datenbank oder Zeitgrenzen eingeschränkt werden. Dadurch würden nur bestimmte Datensätze exportiert und wieder importiert werden. Weiterhin sollte man daran denken die vorhandenen Auswertungsszenarien mit DbRep-Devices bzw. SVG-Diagrammen usw. auch auf die neue Datanbank umzustellen.



Inhalte einer primären Datenbank in eine andere Standby-Datenbank übertragen (syncStandby)

Zweck

Mit dem Export/Import Verfahren kann man, wie oben beschrieben, Daten zwischen verschiedenen Datenbanken austauschen. Mit dem Befehl "syncStandby" ist es aber mit relativ wenig Aufwand möglich, ein regelmäßig automatisch laufendes Übertragungsverfahren zwischen zwei Datenbanken zu etablieren.

Im vorliegenden Beispiel werden Datenbanken des gleichen Typs verwendet. Das Verfahren ist aber auch zwischen Datenbanken unterschiedlichen Typs anwendbar.

Anwendungsfälle dafür sind zum Beispiel:

  • die Einrichtung eines Archivsystems
  • die längerfristge Datenhaltung von Datensätzen eines bestimmten Devices/Readings in einer weiteren Datenbank um sie dort für umfangreiche Auswertungen zu nutzen (z.B. Daten einer PV-Anlage)
  • Wechsel des Datenbanksystems, z.B. von SQLite zu MySQL/MariaDB
  • säubern der Quelldaten von doppelten Datensätzen und nicht mehr benötigten Daten, Weiternutzung der aufgebauten Standbydatenbank als primäre Datenbank nach der Syncronisierung


Für das hier gezeigte Beispielszenario sollen Daten einer MariaDB-Quelldatenbank in einer andere MariaDB-Datenbank regelmäßig übertragen werden. Auf der Quelldatenbank werden die Daten aber nicht gelöscht, d.h. es entstehen zwei Datenbanken mit gleicher Datenbasis.

  • Quelldatenbank ist eine MariaDB "fhemtest1" mit der DbLog-Instanz "LogDB1"
  • Zieldatenbank ist eine MariaDB "fhemtest" mit der DbLog-Instanz "LogStby"



Die Quelldatenbank

Im vorliegenden Beispiel sind die Quelldatenbank und die Standby-Datenbank vom gleichen Typ MariaDB. Das Verfahren funktioniert ebenso zwischen Datenbanken unterschiedlichen Typs, also z.B. zur Übertragung von SQLite Daten in eine MariaDB.

Die Quelldatenbank ist im normalen Kontext die produktive FHEM-Logdatenbank. D.h. sie ist bereits eingerichtet und läuft mit einem entsprechenden DbLog-Device. Die hier verwendete Quelldatenbank heißt "fhemtest1". Die Definition des dazu gehörenden DbLog-Devices "LogDB1" sei der Vollständigkeit halber hier erwähnt, wird aber natürlich bei jedem Nutzer individuell aussehen:

define LogDB1 DbLog ./fhemtest1maria10.conf .*:(?!done).*
attr LogDB1 DbLogInclude CacheUsage
attr LogDB1 DbLogSelectionMode Exclude/Include
attr LogDB1 DbLogType History
attr LogDB1 addStateEvent 0
attr LogDB1 asyncMode 1
attr LogDB1 bulkInsert 1
attr LogDB1 cacheEvents 2
attr LogDB1 cacheLimit 2000
attr LogDB1 dbSchema fhemtest1
attr LogDB1 devStateIcon .*active:10px-kreis-gelb connected:10px-kreis-gruen .*disconnect:10px-kreis-rot
attr LogDB1 disable 0
attr LogDB1 room DbLog
attr LogDB1 showproctime 1
attr LogDB1 syncInterval 120
attr LogDB1 useCharfilter 1
attr LogDB1 verbose 2



Die Standby Datenbank

Die Standby Datenbank ist das Synchronisationsziel. Die Erstellung der Datenbank und die nachfolgende Definition des dazu gehörigen DbLog-Devices erfolgt weitgehend wie für eine "normale" Log-Datenbank wie in der Commandref beschrieben mit geringfügigen Anpassungen.

1. Anlegen der DB und Tabellen
wie in diesem Link hinterlegt, erfolgt die Erstellung mit den Befehlen für MySQL:
CREATE DATABASE `fhemtest` DEFAULT CHARACTER SET utf8 COLLATE utf8_bin;
CREATE USER 'fhemuser'@'%' IDENTIFIED BY 'fhempassword';
CREATE TABLE `fhemtest`.`history` (TIMESTAMP TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, DEVICE varchar(64), TYPE varchar(64), EVENT varchar(512), READING varchar(64), VALUE varchar(128), UNIT varchar(32));
ALTER TABLE `fhemtest`.`history` ADD PRIMARY KEY(TIMESTAMP, DEVICE, READING);
CREATE TABLE `fhemtest`.`current` (TIMESTAMP TIMESTAMP, DEVICE varchar(64), TYPE varchar(64), EVENT varchar(512), READING varchar(64), VALUE varchar(128), UNIT varchar(32));
GRANT SELECT, INSERT, DELETE, UPDATE ON `fhemtest`.* TO 'fhemuser'@'%';

Es wird zusätzlich zu der normalen Erstellung der history-Tabelle ein primary Key für die Tabelle erstellt. Dieser Key verhindert dass Duplikate in der Tabelle erstellt werden und erleichtert dadurch sehr das Verfahren der Datenübertragung in der Folge.


DbLog-Device für Standby-Datenbank
2. Erstellung der Konfigurationsdatei und Definition des DbLog-Devices für die Standby-Datenbank
Die Konfigurationsdatei (mariastby.conf) beinhaltet die Verbindunginformationen für das DbLog-Device und wird genau wie in der DbLog-Commandref beschrieben angelegt. Das DbLog-Device für die Standby-Datenbank wird nur für die nachfolgende Definition des benötigten DbRep-Devices gebraucht und wird so definiert, dass keinerlei Events geloggt werden. Das kann auf verschiedenen Wegen erreicht werden. Im Beispiel wird der Regex im DEF entsprechend aufgebaut.
define LogStby DbLog ./mariastby.conf aaaaaa:bbbbbb
attr LogStby DbLogType History
attr LogStby asyncMode 1
attr LogStby bulkInsert 1
attr LogStby cacheEvents 2
attr LogStby devStateIcon .*active:10px-kreis-gelb connected:10px-kreis-gruen .*disconnect:10px-kreis-rot
attr LogStby disable 0
attr LogStby room DbLog
attr LogStby showNotifyTime 1
attr LogStby showproctime 1
attr LogStby syncEvents 1


Die Erstellung der current-Tabelle ist für den vorgesehenen Zweck eigentlich nicht nötig, wird der Vollständigkeit halber mit dokumentiert. Ist das DbLog-Device definiert und erfolgreich verbunden, ist dessen state "connected".



Definition des DbRep-Devices zur Synchronisation Quell- und Standby-Datenbank

Es existieren nun das DbLog Device "LogDB1" für die produktive Quelldatenbank und das DbLog Device "LogStby" für die Standby-Datenbank. Für die Synchronisation wird ein DbRep-Device erstellt, welches mit dem DbLog-Device der produktiven Quelldatenbank, also LogDB1, verbunden wird. Wie üblich, wird im DEF der Name des entsprechenden DbLog-Devices angegeben, hier "LogDB1".

defmod Rep.LogDB1.syncStandby DbRep LogDB1
attr Rep.LogDB1.syncStandby aggregation no
attr Rep.LogDB1.syncStandby event-on-update-reading state
attr Rep.LogDB1.syncStandby fastStart 1
attr Rep.LogDB1.syncStandby role Client
attr Rep.LogDB1.syncStandby room DbLog
attr Rep.LogDB1.syncStandby showproctime 1
attr Rep.LogDB1.syncStandby stateFormat { (ReadingsVal($name,"state", ""))." : SQL-Zeit: ".(ReadingsVal($name,"sql_processing_time", "")) }
attr Rep.LogDB1.syncStandby timeDiffToNow d:6
attr Rep.LogDB1.syncStandby verbose 3

Mit den verschiedenen möglichen Zeitattributen (time.*) und den Attributen device und reading wird abgegrenzt, welche Datensätze in die Standby-Datenbak übertragen werden sollen. In dem vorliegenden Beispiel werden mit jedem Synchronisationslauf alle in der DB vorhandenen Datensätze, die nicht älter als 6 Tage Tage sind, in die Standby-Datenbank übertragen.

Hinweis:
Würde man einen solchen Lauf z.B. alle 4 Stunden durchführen, würden viele doppelte Datensätze in der Datenbank entstehen. Der bei der Tabellenerstellung für die history-Tabelle angelegte primary Key verhindert das !

syncStandby gestartet


Ein Synchronisationslauf wird nun gestartet mit:

 set <DbRep-Name> syncStandby <DbLog-Device Standby>

In dem vorliegenden Beispiel ist das:

 set Rep.LogDB1.syncStandby syncStandby LogStby
syncStandby erfolgreich ausgeführt

Der Fortschritt der Datenübertragung sowie die evtl. eingefügten Datensätze (number of lines inserted) ist im Logfile mit verbose 3 ersichtlich:

2020.01.24 17:12:41.186 3: DbRep Rep.LogDB1.syncStandby - total lines transfered to standby database: 6177
2020.01.24 17:12:46.411 3: DbRep Rep.LogDB1.syncStandby - total lines transfered to standby database: 23957
2020.01.24 17:12:51.710 3: DbRep Rep.LogDB1.syncStandby - total lines transfered to standby database: 24048
2020.01.24 17:12:57.733 3: DbRep Rep.LogDB1.syncStandby - total lines transfered to standby database: 24092
2020.01.24 17:13:03.505 3: DbRep Rep.LogDB1.syncStandby - total lines transfered to standby database: 24059
2020.01.24 17:13:08.891 3: DbRep Rep.LogDB1.syncStandby - total lines transfered to standby database: 23969
2020.01.24 17:13:13.420 3: DbRep Rep.LogDB1.syncStandby - total lines transfered to standby database: 17871
2020.01.24 17:13:13.421 3: DbRep Rep.LogDB1.syncStandby - number of lines inserted into "LogStby": 104
nur Daten der Devices SMA_Energymeter,MySTP_5000 werden übertragen

Nach dem erfolgreichen Sync-Lauf wird die benötigte Laufzeit und die Anzahl der übertragenen Datensätze in Readings angezeigt.

Mit dem Attribut aggragation wird gesteuert, in welchen Zeitscheiben die Selektion der Daten aus der Quelldatenbank und die Übertragung ausgeführt wird. Im Standard ohne gesetztes aggregation-Attribut ist es ein Tag. Stehen genügend Ressourcen zur Verfügung, kann dieser Wert auch auf "week" oder "month" gesetzt werden. Der Wert "hour" zur Verkleinerung des Datenpaketes ist ebenfalls möglich.

Bei jedem Lauf wird die Anzahl der eingefügten Datensätze im Reading number_lines_inserted_Standby angezeigt.

Die zu übertragenden Daten können natürlich sehr granular bestimmt werden. Sollen zum Beispiel nur die Daten der Devices SMA_Energymeter und MySTP_5000 übertragen werden, wird das Attribut device entsprechend gesetzt:

attr Rep.LogDB1.syncStandby SMA_Energymeter,MySTP_5000


Weiterhin bietet das Attribut reading eine Eingrenzung auf die gewünschten Readings und mit dem Attribut valueFilter kann eine Eingrenzung nur auf bestimmte vorkommende Werte vorgenommen werden.

Der Erfolg der Synchronisationsläufe kann sehr einfach mit einem separaten DbRep-Device (mit dem fetchrows Kommando) oder dem Tool phpMyAdmin (nebenstehend) überprüft werden.



regelmäßige Ausführung der Synchronisation

Ein einfaches AT kann dazu dienen den Synchronisationslauf mit dem Device Rep.LogDB1.syncStandby regelmäßig alle x-Stunden/Minuten auszuführen.

define At.Sync.Stby at +*04:00:00 set Rep.LogDB1.syncStandby syncStandby LogStby
attr At.Sync.Stby room DbLog



Ermittlung und Darstellung der täglichen Energieerzeugung eines Wechselrichters

Gegeben sei dass die Energiedaten eines Wechselrichters (STP_5000) mit SMAUtils in Verbindung mit SBFSpot in die Datenbank geschrieben werden. Neben vielen anderen Werten liefert SMAUtils die Tageserzeugung in kWh als Reading "etoday". Der Wert dieses Readings wird alle paar Minuten in der Datenbank gespeichert.

Zur Auswertung wird ein DbRep-Device angelegt wie unter Definieren eines DbRep-Devices) beschrieben (z.B. Rep.etoday.STP_5000).

Rep.STP_5000 konfiguriert

Es soll die täglich erzeugte Energiemenge beginnend ab dem 01.10.2016 0 Uhr bis zum 13. Oktober angezeigt werden.

attr Rep.etoday.STP_5000 timestamp_begin 2016-10-01 00:00:00
attr Rep.etoday.STP_5000 timestamp_end 2016-10-13 23:59:59

Da es sich um eine tägliche Aggregation handelt (es soll pro Auswertung der Tag von 00:00:00 bis 23:59:59 betrachtet werden) und die Hintergrundverarbeitungszeit dargestellt werden soll, wird eingestellt:

attr Rep.etoday.STP_5000 aggregation day
attr Rep.etoday.STP_5000 showproctime 1

Die Eingrenzung der auszuwertenden Devices und der dazu gehörigen Readings wird durch die entsprechenden Attribute gewährleistet. Es soll nur das Device "STP_5000" mit dem Reading "etoday" berücksichtigt werden. Dazu stellen wir ein:

attr Rep.etoday.STP_5000 reading etoday
attr Rep.etoday.STP_5000 device STP_5000

DbRep ist nun grundsätzlich zur Auswertung konfiguriert (siehe Screenshot).

Zur Selektion und Berechnung von numerischen Daten stehen die Funktionen average-, max-, min-, sum- und diffValue zur Verfügung. Bei den in diesem Beispiel verwendeten Daten handelt es sich um einen täglich neu erzeugten und über 24 Stunden stetig steigenden Wert. Demnach ist für diese Art Daten die Funktion "maxValue" geeignet. Sie gibt den maximalen Wert des Readings im eingestellten Aggregationszeitraum (day) aus.

Rep.STP_5000 maxValue

Die Berechnung wird ausgeführt durch:

set Rep.etoday.STP_5000 maxValue

Danach kurzer Zeit ist die Berechnung erfolgt (state=done in DeviceOverview). Nach einem Browserrefresh in der Detailansicht sind die erzeugten Readings sichtbar.

Jedes Reading hat einen bestimmten Aufbau. Die Funktion "maxValue" erzeugt Readings folgenden Aufbaus.

2016-10-07_18-19-03__STP_5000__etoday__MAX__2016-10-07

Zunächst wird der Timestring "2016-10-07_18-19-03" ausgegeben der in diesem Kontext den höchsten (d.h. letzten) Wert von "etotal" an dem Tag markiert. Bei dem Wechselrichter ist es damit auch der Zeitpunkt zu dem die Energieerzeugung eingestellt wurde (in dem Fall 07.10.2016 um 18:19:03).


Anmerkung: Die Notation des Timestrings wurde so gewählt, um die Regeln für in Readings erlaubte Zeichen einzuhalten. Wenn keine auswertbaren Daten vorliegen und berechnet werden konnten, werden Readings mit "-" ausgegeben. Im Beispiel sind es die Tagesaggregate 10.10.-13.10. da diese Tage in der Zukunft liegen.


Danach folgt im Reading die Angabe des Devices (STP_5000), des augewerteten Readings (etoday) und der Funktion die dieses Ergebnis ermittelt hat (MAX für maxValue). Die letzte Angabe, hier "2016-10-07" definiert den Auswertungszeitraum. In dem Beispiel ist es der 07.10.2016. Würde die Berechnung mit aggregation=week durchgeführt werden, würde man als an dieser Stelle den Auswertungszeitraum "week_39" erhalten, also die Kalenderwoche 39.


Um die Auswertungsergebniss etwas benutzerfreundlicher zu gestalten, steht das Attribut "readingNameMap" zur Verfügung.

Rep.STP_5000 maxValue mit readingNameMap

Im Beispiel setzen wir es auf:

attr Rep.etoday.STP_5000 readingNameMap Tageserzeugung_kWh

Ein erneuter maxValue-Lauf erzeugt Readings des folgenden Aufbaus:

2016-10-01_18-03-13__Tageserzeugung_kWh__2016-10-01   4.9870



(regelmäßiges) Löschen von Datenbanksätzen

Dieses DbRep-Device dient dazu einmalig oder verbunden mit einem AT-Device Einträge aus einer Datenbank zu löschen. Zunächst wird das Device wie im Abschnitt "Definieren eines DbRep-Devices" beschrieben definiert. Dadurch wird das DbRep-Device mit einem DbLog-Device assoziiert und mit der Datenbank des DbLogs verbunden. Alle weiteren Operationen werden mit dieser Datenbank durchgeführt. In diesem Beispiel heißt das DbRep-Device "Rep.Del.DbShort" und das DbLog-Device "LogDBShort".

In diesem Beispiel sollen alle Datensätze gelöscht werden die älter als 180 Tage sind. Die Löschfunktion des Moduls ist standardmäßig ausgeschaltet und muß per Attribut enabled werden.

Dazu werden die Attribute

Rep.Del.DbShort konfiguriert
 attr Rep.Del.DbShort timeOlderThan d:180
 attr Rep.Del.DbShort allowDeletion 1

gesetzt. Die Zeitangabe erfolgt in Sekunden.

Insbesondere bei SQLite sollte während des Löschens kein paralleler Schreibprozess in Form des normalen Eventloggings stattfinden. MySQL/MariaDB kann es durchaus parallel verarbeiten. Um einen parallelen Zugriff durch DbLog-Loggging zu verhindern, kann das Logging vor der Löschung geschlossen und danach wieder geöffnet werden. Dafür werden die Attribute mit z.B. diesen Werten gesetzt:

 attr Rep.Del.DbShort executeBeforeProc set LogDBShort reopen 7200
 attr Rep.Del.DbShort executeAfterProc set LogDBShort reopen

Dabei ist "LogDB" das mit dem DbRep-Device assoziierte DbLog-Device.

Der Löschvorgang wird mit

 set Rep.Del.DbShort delEntries

gestartet. Nach Abschluß der Datenlöschung wird die Anzahl der deleted rows als Reading ausgegeben und auch im Log mit verbose=3 geschrieben.

Müssen sehr viele Datensätze gelöscht werden, könnte nach 86400 Sekunden der Standardtimeout erreicht werden und der Vorgang mit "error" enden. In diesem Fall ist der timout mit dem Attribut:

 attr Rep.Del.DbShort timeout <timeout in Sekunden>

entsprechend angepasst werden.

Ein fhem.cfg Eintrag für das beschriebene Beispieldevice sieht folgendermaßen aus:

define Rep.Del.DbShort DbRep LogDBShort
attr Rep.Del.DbShort allowDeletion 1
attr Rep.Del.DbShort comment löschen aller Einträge in LogDBShort älter als 180 Tage
attr Rep.Del.DbShort devStateIcon connected:10px-kreis-gelb .*disconnect:10px-kreis-rot .*done:10px-kreis-gruen
attr Rep.Del.DbShort event-on-update-reading state
attr Rep.Del.DbShort room DbLog
attr Rep.Del.DbShort timeOlderThan 15552000

Die Löschung der relevanten Datensätze kann weiterhin über Attribute "device" bzw. "reading" kann selektiv auf bestimmte Device- und/oder Readingloggings beschränkt werden. Wird keine Zeitbeschränkung (z.B. durch "timeOlderThan") vorgenommen, erfolgt die Löschung aller Datensätze der durch "device" bzw. "reading" spezifizierten Datenbankinhalte.

Als Argument der Attribute "device" bzw. "reading" kann SQL-Wildcard "%" verwendet werden.

%     substituiert ein oder mehrere Zeichen
_     Platzhalter für ein einzelnes Zeichen

Beispiel:

 attr Rep.Del.DbShort device %5000

Mit dieser Spezifikation werden alle Devices gelöscht die auf "5000" enden, also z.B. STP_5000 und MySTP_5000. (siehe auch Attribut "reading" in der Commandref).

Ein einfaches AT kann dazu dienen den Löschjob über das Device Rep.Del.DbShort regelmäßig alle x-Stunden/Minuten auszuführen. Dadurch werden nicht mehr benötigte Datenbankeinträge automatisiert und nicht FHEM blockierend im Hintergrund entfernt.

define At.Del.DbShort at +*23:45:00 set Rep.Del.DbShort delEntries
attr At.Del.DbShort room DbLog

erweiterte Anwendung von Selektionsbedingungen zur Löschung

Wie bei allen selektiven Funktionen im DbRep kann auch bei der Löschfunktion über die Attribute device und reading eine Beschränkung der zu löschenden Datensätze erfolgen. D.h. es werden nur Datensätze gelöscht, wenn die Selektionskriterien dieser Attribute erfüllt sind.

Im Attribut device können einzelne Geräte, Geräte-Spezifikationen (devspec) sowie Geräte mit Wildcards angegeben werden. Ist eine devspec angegeben, werden die Devicenamen vor der Selektion aus der Geräte-Spezifikationen und den aktuell in FHEM vorhandenen Devices aufgelöst sofern möglich. Wird dem Device bzw. der Device-Liste oder Geräte-Spezifikation ein "EXCLUDE=" vorangestellt, werden diese Devices von der Selektion und damit von dem Löschvorgang ausgeschlossen.

attr <name> device TYPE=DbRep                    # es werden nur Datensätze der Geräte vom Modul-Typ "DbRep" 
                                                   eingeschlossen
attr <name> device MySTP_5000                    # nur Datensätze des Gerätes "MySTP_5000" werden selektiert/gelöscht
attr <name> device SMA.*,MySTP.*                 # Daten von Geräten die mit "SMA" oder "MySTP" beginnen, werden 
                                                   selektiert/gelöscht
attr <name> device SMA_Energymeter,MySTP_5000    # nur Daten der beiden angegebenen Geräten werden selektiert/gelöscht
attr <name> device %5000                         # Daten von Geräten die mit "5000" enden werden in die Operation 
                                                   eingeschlossen
attr <name> device TYPE=SSCam EXCLUDE=SDS1_SVS   # es werden Datensätze der Geräte vom Modul-Typ "SSCam" eingeschlossen, 
                                                   aber das Gerät "SDS1_SVS" (auch vom Typ SSCam) wird ausgeschlosssen
attr <name> device EXCLUDE=SDS1_SVS              # Datensätze aller Geräte mit Ausnahme des Gerätes "SDS1_SVS" werden          
                                                   selektiert/gelöscht
attr <name> device EXCLUDE=TYPE=SSCam            # Datensätze aller Geräte mit Ausnahme der Geräte vom Typ "SSCam" werden          
                                                   selektiert/gelöscht


Ähnlich erfolgt die Abgrenzung der DB-Selektionen auf ein bestimmtes oder mehrere Readings sowie exkludieren von Readings. Mehrere Readings werden als Komma separierte Liste angegeben. Es können SQL Wildcard (%) verwendet werden, aber keine Perl-Regex. Wird dem Reading bzw. der Reading-Liste ein "EXCLUDE=" vorangestellt, werden diese Readings von der Selektion ausgeschlossen.

attr <name> reading etotal                                     # Datensätze mit dem Reading "etotal" werden eingeschlossen 
attr <name> reading et%                                        # Datensätze deren Reading mit "et" beginnt werden 
                                                                 eingeschlossen
attr <name> reading etotal,etoday                              # Datensätze mit den Readings "etotal" und "etoday" werden 
                                                                 in die Selektion eingeschlossen
attr <name> reading eto%,Einspeisung EXCLUDE=etoday            # Datensätze mit Readings beginnend mit "eto" sowie das 
                                                                 Reading "Einspeisung" werden selektiert, aber das Reading 
                                                                 "etoday" wiederum ausgeschlossen
attr <name> reading etotal,etoday,Ein% EXCLUDE=%Wirkleistung   # Datensätze mit Readings beginnend mit "Ein" sowie die 
                                                                 Readings "etotal" und "etoday" werden selektiert, wobei 
                                                                 Readings, die auf "Wirkleistung" enden, von der Löschung
                                                                 ausgeschlossen werden


Natürlich werden die Selektionsbedingungen durch eventuell gesetzte Zeiteingrenzungen mit den time*- Attributen ergänzt. Die time*- Attribute sind mehrere im DbRep vorhandene Attribute, die alle mit "time" beginnen, z.B. timeOlderThan.

Auffinden von alten Devicenamen in der DB und versenden/loggen einer Negativliste

Wenn eine Datenbank längere Zeit gelaufen ist, Devices in FHEM hinzugefügt, geändert oder gelöscht wurden oder auch das DEF des DbLog-Devices angepasst wurde, befinden sich aller Wahrscheinlichkeit nach nicht mehr gewünschte/gebrauchte Datensätze von nicht mehr bestehenden Devices in der Datenbank.

Dieses Beispiel soll einen Weg zeigen, wie man mit Hilfe der DbRep-sqlCmd Funktion (ab Version 4.14.0) alle in der DB enthältenen Devicenamen ermitteln und mit einer Positivliste vergleichen kann. Die Negativliste der nicht gewünschten Devices wird im Logfile ausgegeben bzw. mit TelegramBot versendet.

Zunächst wird das Device wieder wie im Abschnitt "Definieren eines DbRep-Devices" beschrieben definiert. Dadurch wird das DbRep-Device mit einem DbLog-Device assoziiert und mit der Datenbank des DbLog-Device verbunden. In diesem Beispiel heißt das DbRep-Device "Report".

Nach der Definition setzen wir die Attribute:

attr Report event-on-update-reading state
attr Report sqlResultFormat separated

Es werden nun nur noch Events für "state" generiert. Das Attribut "sqlResultFormat = separated" bewirkt, dass das Ergebnis des SQL-Statements zeilenweise in Einzelreadings zur Verfügung gestellt wird.

Nun kann getestet werden, ob die Abfrage grundsätzlich funktioniert. Dazu wird das benutzerspezifische Kommando "sqlCmd" verwendet.

set Report sqlCmd select device, count(*) from history group by DEVICE;

Es sollte nun eine entsprechende Anzahl von Readings erzeugt werden (Browserrefresh), die als Wert eine Kombination aus <Device>|<Anzahl Datensätze in DB> enthalten wie im nebenstehenden Screenshot ersichtlich.

sqlCmd ausgeführt

Wenn das Ergebnis wie gewünscht vorliegt, wird nun die Benutzerschnittstelle aktiviert.

attr Report userExitFn NaDevs .*:.*

"NaDevs" ist die Funktion in 99_myUtils.pm die aufgerufen werden soll. Die Angabe des Regex ".*:.*" ist optional. Nähere Informationen zur Funktionsweise sind in der commandref/DbRep zu finden.

Die "Positivliste" der in der Datenbank erlaubten Devices wird für das Beispiel in dem Attribut "comment" für den späteren Vergleich als String angegeben. Es ist natürlich auch z.B. eine Ableitung der im DEF-Regex des zugeordneten DbLog-Devices enthaltenen Device-Definitionen vorstellbar, sodass immer der aktuelle Stand dieser Definition als Grundlage für den Vergleich herangezogen wird.

attr Report comment (sysmon|MyWetter|SMA_Energymeter|STP_5000|Cam.*)

Somit werden die Devices "sysmon", "MyWetter", "SMA_Energymeter", "STP_5000" und alle Cam-Devices als in der DB erlaubt definiert. Alle anderen gefundenen Devices sollen in einer Negativliste aufgeführt werden.

Im weiteren Schritt wird die aufzurufende Funktion in der vorhandenen 99_myUtils.pm ergänzt.

############################################################################
# $Id: myUtilsTemplate.pm 7570 2015-01-14 18:31:44Z rudolfkoenig $
#
# Save this file as 99_myUtils.pm, and create your own functions in the new
# file. They are then available in every Perl expression.

package main;

use strict;
use warnings;

sub
myUtils_Initialize($$)
{
  my ($hash) = @_;
}

# Enter you functions below _this_ line.

my @dna;

...

######################################################
########    Not allowed Devs in DB ermitteln
########          UserExitFn in DbRep           
######################################################
sub NaDevs {
 my ($name,$reading,$value) = @_;
 my $hash   = $defs{$name};
 
 # DB Namen ermitteln
 my $dbconn = $hash->{dbloghash}{dbconn};
 my $db = (split("=",(split(";",$dbconn))[0]))[1];
 
 # Liste der erlaubten Devices aus Attribut "comment" lesen
 my $adevs = AttrVal($name, "comment","-");
 
 if($reading eq "state" && $value eq "running") {
     # der Select wurde gestartet
     Log3 $name, 1, "UserExitFn called by $name - new Select has been startet. Allowed devices in database are: $adevs"; 
     @dna=();
     return;
 }
 
 my $d = (split("\\|",$value))[0];
 
 # das Selektionsergebnis bewerten und ggf. dem Ergebnisarray hinzufügen
 if ( $reading =~ m/SqlResultRow.*/ && $d !~ m/$adevs/) {
   push(@dna,$d."\n")
 }
 
 if ($reading eq "state" && $value eq "done") {
     # Ergebnis versenden bzw. loggen
     Log3 $name, 1, "UserExitFn called by $name - Devices not allowd found in $db: @dna"; 
     fhem("set teleBot message Devices are not allowed found in $db: \n @dna");
 }
 
return;
}

....
1;

Die Schnittstelle übergibt der aufgerufenen Funktion den Namen des DbRep-Devices, den Namen des erstellten Readings und dessen Wert. Der Aufruf erfolgt nach jeder Readingserstellung. Die Schnittstelle arbeitet OHNE Eventgenerierung bzw. benötigt keine Events. Über den Namen des DbRep-Devices kann auf dessen Hash zugegriffen werden bzw. über $hash->{dbloghash}{...} ebenfalls auf den Hash des angeschlossenen DbLog-Devices.

In der Funktion "NaDevs" wird der Device-Teilstring des erzeugten Readings mit dem Wertevorrat aus dem "comment"-Attribut verglichen und bei einem negativen Ergebnis der Negativliste @dna hinzugefügt.

Nach Abschluss der Operation (signalisiert durch Reading "state = done") wird die erzeugte Liste im Logfile ausgegeben bzw. ein TelgramBot-Device versendet.

Größe der FHEM-Datenbank ermitteln

Zunächst wird das Device wie im Abschnitt "Definieren eines DbRep-Devices" beschrieben definiert. Dadurch wird das DbRep-Device mit einem DbLog-Device assoziiert und mit der Datenbank des DbLogs verbunden. Alle weiteren Operationen werden mit dieser Datenbank durchgeführt. In diesem Beispiel heißt das DbRep-Device "Rep.Fhem.Size".

Um die Größe der Datenbank beziehungsweise der Tabellen "history" und "current" zu ermitteln steht das Kommando:

get Rep.Fhem.Size tableinfo
 get Rep.Fhem.Size tableinfo  (MySQL, PostgreSQL)
 get Rep.Fhem.Size svrinfo    (SQLite)

zur Verfügung.

Mit diesem Befehl werden sehr viele Informationen bezüglich der Tabellen des FHEM-Schemas (MySQL) als Readings ausgegeben.

Um nur die relevanten bzw. interessierenden Selektionsergebnisse auszugeben, kann mit dem Attribut showTableInfo (MySQL, PostgreSQL) bzw. showSvrInfo (SQLite) eine kommaseparierte Liste der relevanten Tabellen angegeben werden.

attr Rep.Fhem.Size showTableInfo %history%,%current%    (MySQL, PostgreSQL)

So wie in diesem Beispiel gesetzt, werden nur Informationen der Tabellen "history" und "current" ausgegeben. Diese Ausgabe ich bereits recht übersichtlich, kann aber durch die Verwendung des Attributs suppressReading weiter eingegrenzt werden.

Beispiele:

attr Rep.Fhem.Size suppressReading ^(?!.*INFO_history.data_index_length_MB).*$
attr Rep.Fhem.Size suppressReading ^(?!.*(INFO_history.data_index_length_MB)|(state)).*$
attr Rep.Fhem.Size suppressReading ^(?!.*(INFO_history.data_index_length_MB)|(background_processing_time)|(state)).*$

Mit diesen Attributwerten wird nur das Reading "INFO_history.data_index_length_MB" oder aber "INFO_history.data_index_length_MB" und "state" bzw. "INFO_history.data_index_length_MB", "state" und "background_processing_time" dargestellt.

Die Datenbankgröße (MB) ist je nach DB-Typ in den Readings:

INFO_current.data_index_lenth_MB
INFO_history.data_index_lenth_MB
SQLITE_FILE_SIZE_MB

enhalten.

Wird z.B. über ein AT dieser Befehl regelmäßig durch Rep.Fhem.Size ausgeführt und die Ergebnisse wiederum in DbLog gespeichert kann die Größenentwicklung der Datenbank mittels SVG-Diagrammen dargestellt werden.

get Rep.Fhem.Size tableinfo


Readingwerte von DbRep in ein anderes Device übertragen

Um Readings aus DbRep in einen Dummy zu übertragen, können verschiedene Verfahren angewendet werden. Allen Verfahren ist gemeinsam, dass sie auf die Arbeitsweise der Funktionen von DbRep Rücksicht nehmen. Alle Funktionen von DbRep arbeiten, von Ausnahmen abgesehen, asynchron (non-blocking). Das hat den Vorteil, dass die Datenbankverarbeitungszeit bzw. eine Nichtverfügbarkeit der DB nicht hemmend auf FHEM wirkt, hat andererseits aber den Nachteil, dass die Ergebnisse einer Funktion nicht direkt nach dem Funktionsaufruf zur Verfügung stehen und somit nicht trivial innerhalb der FHEM-Hauptschleife weiterverarbeitet werden können.


Werte automatisch durch DbRep übertragen lassen (ab Version 8.40.0)

Ab DbRep Version 8.40.0 gibt es das Attribut autoForward mit dem eine integrierte Übertragung der DbRep-Ergebnissreadings in andere Devices (z.B. Dummy) eingerichtet werden kann. Diese Variante wird hier beschrieben.

Die weiteren manuellen Varianten werden in den nachfolgenden Abschnitten behandelt.

Um die integrierte Reading-Weiterleitung zu aktivieren wird des Attribut autoForward nach folgender Systematik gesetzt:

Übertragung aller Readings nach Dum.Rep.all
{
  "<source-reading> => "<destination device> [=> <destination-reading>]",
  "<source-reading> => "<destination device> [=> <destination-reading>]",
  ...
}          
Übertragung Readings mit "SUM" nach Dum.Rep.Sum

In der Spezifikation von <source-reading> können Wildcards (.*) verwendet werden um nur eine Auswahl der erzeugten Readings an das Zieldevice <destination device> weiterzuleiten. Ist der optionale Zusatz <destination-reading> angegeben, erfolgt die Speicherung der übertragenen Readings im Zieldevice mit dem angegebenen Namen.
Der angegebene Readingname muss den Regularien zur Namensgebung von Readings genügen. Eventuell vorhandene nicht erlaubte Zeichen werden durch "_" ersetzt.

Fehlt der Zusatz <destination-reading>, werden die in DbRep erzeugten Readings 1:1 in das Zieldevice übertragen.

Beispiel:

attr DbRepDev autoForward {
                            ".*"        => "Dum.Rep.All",     
                            ".*AVGAM.*" => "Dum.Rep     => average",
                            ".*SUM.*"   => "Dum.Rep.Sum => summary",
                          }

Die Spezifikation erfüllt folgende Ziele:

  1. alle Readings werden zum Device "Dum.Rep.All" übertragen, der ursprüngliche Readingname bleibt im Ziel erhalten (Screenshot 1)
  2. Readings mit "AVGAM" im Namen werden zum Device "Dum.Rep" in das Reading "average" übertragen
  3. Readings mit "SUM" im Namen werden zum Device "Dum.Rep.Sum" in das Reading "summary" übertragen (Screenshot 2)



Werte mittels Event übetragen

Es sollen zum Beispiel die erzeugten Readings aus einem DbRep-Device in einen Dummy übetragen werden, die den Term "Grid" im Wortstamm tragen.

Ein Reading-Name der fetchrows-Funktion in DbRep ist immer nach folgendem Schema aufgebaut:

<Datum>_<Zeit>__<Unique-Index>__<Device>__<Reading>__<Dopplerindex>
z.B.: 2018-10-14_18-30-25__1__MyWetter__humidity

Datum und Zeit entsprechen dem Timestamp des gespeicherten Events in der Datenbank. Der Unique-Index identifiziert eventuell vorhandene Doubletten in der DB und der Dopplerindex ist ein Hilfsmittel, Datensätze die sich allein durch den Value-Wert unterscheiden, auch als unterschiedliche Readings erstellen zu können. Dieser Index wird nur bei Bedarf erstellt.

Im folgenden Beispiel werden alle erzeugten Readings mit "Grid" im Namen des DbRep-Devices "Rep.SMAEM" in den Dummy übertragen und heißen dann genauso. Zunächst wird der Dummy angelegt.

define Dum.Rep dummy
attr Dum.Rep room DbLog

Mit dem nachfolgend angelegten Notify wird auf die z.B. von fetchrows erzeugten Events reagiert, der Event entsprechend gesplittet und verarbeitet in den Dummy übertragen.

define N.Dum.Rep notify Rep.SMAEM:(\d).*Grid.* { fhem "setreading Dum.Rep ".(split(":",$EVTPART0))[0]." $EVTPART1"}
attr N.Dum.Rep room DbLog

Soll im Dummy ein Reading mit eigenem Namen gefüllt werden, sieht das Notify z.B. so aus:

define N.Dum.Rep notify Rep.SMAEM:(\d).*Grid.* { fhem "setreading Dum.Rep DeinReading"." $EVTPART1"}


Werte mittels Funktion DbReadingsVal übertragen

Sobald ein DbRep-Device definiert ist, wird die Funktion DbReadingsVal zur Verfügung gestellt. Mit dieser Funktion läßt sich, ähnlich dem allgemeinen ReadingsVal, der Wert eines Readings aus der Datenbank abrufen. Die Befehlssyntax ist:

   DbReadingsVal("<name>","<device:reading>","<timestamp>","<default>") 

Mit dieser Funktion kann ein Device/Reading-Wert aus der Datenbank gelesen und in einen Dummy gesetzt werden. "Name" ist dabei das abzufragende DbRep-Device. Der Timestamp muss nicht genau bekannt sein. Es wird der zeitlich zu <timestamp> passendste Readingwert zurück geliefert, falls kein Wert exakt zu dem angegebenen Zeitpunkt geloggt wurde. Um den aktuellsten in der Datenbank gespeicherten Wert eines Readings abzurufen, kann als Timestamp die aktuelle Zeit entsprechend formatiert verwendet werden.

Es soll z.B. der aktuellste Datenbankwert des Readings "etoday" vom Device "MySTP_5000" ausgelesen und im Dummy "Dum.Rep" als Reading "EnergyToday" gespeichert werden. Das beteilgte DbRep-Device heißt "Rep.Energy".

Für die Formatierung des aktuellen Timestamps kann die FHEM-Funktion FmtDateTime verwendet werden, welcher der aktuelle UNIX-Timestamp übergeben wird:

FmtDateTime(time)

Der Datenabruf aus der Datenbank sieht dann folgendermaßen aus:

DbReadingsVal("Rep.Energy","MySTP_5000:etoday",FmtDateTime(time),"") 

Mit dem Befehl setreading kann der abgerufene Wert in dem Dummy-Device gespeichert werden:

{ fhem "setreading Dum.Rep EnergyToday ".DbReadingsVal("Rep.Energy","MySTP_5000:etoday",FmtDateTime(time),"") }

Damit der Datenabruf regelmäßig erfolgt und immer der aktuellste Wert von "etoday" in den Dummy eingtragen wird, kann dieses AT verwendet werden:

 define set.Dum.Rep_EnergyToday at +*00:01:00 { fhem "setreading Dum.Rep EnergyToday ".DbReadingsVal("Rep.Energy","MySTP_5000:etoday",FmtDateTime(time),"") }

Somit erfogt eine Aktualisierung des Readings "EnergyToday" im Dummy "Dum.Rep" jede Minute. Es ist zu beachten, dass die Funktionsausführung von DbReadingsVal blockierend erfolgt. Sollte die Datenbank nicht verfügbar sein oder lange Antwortzeiten besitzen, hat dies negative Auswirkungen auf FHEM, was sich in Blockierungszuständen äußert. Möchte man solche eventuell möglichen Zustände vermeiden, kann die Befehlsausführung in die 99_myUtils unter Verwendung von Blocking Call ausgelagert werden.

Diese non-blocking Variante der Verwendung von DbReadingsVal wird nachfolgend skizziert. In der 99_myUtils wird dazu der nachfolgende Code eingefügt, der drei kleine Subroutinen enthält:

############################################################################################################
#         DumRepEnergyToday (Reading mittels DbReadingsVal non-blocking setzen)    
############################################################################################################
sub DumRepEnergyToday ($$$$$) {
 # initiale Routine zum Aufruf von BlockingCall unter Berücksichtigung von "RUNNING_SET_READING"
 my ($name,$device,$reading,$destdev,$destread) = @_;
 my $hash = $defs{$name};

 return if($hash->{HELPER}{RUNNING_SET_READING});
 $hash->{HELPER}{RUNNING_SET_READING} = BlockingCall("DumRepEnergyTodayNbl", "$name|$device|$reading|$destdev|$destread", "DumRepEnergyTodayNblDone");
 
return;
}

sub DumRepEnergyTodayNbl($) {
 # die eigentliche Routine in der die potentiell blockierende Funktion ausgeführt wird
 my ($string) = @_;
 my ($name,$device,$reading,$destdev,$destread) = split("\\|", $string);
 my $hash = $defs{$name};

 my $val = DbReadingsVal($name,"$device:$reading",FmtDateTime(time),"");
 $val = encode_base64($val,"");
 
return "$name|$val|$destdev|$destread";
}

sub DumRepEnergyTodayNblDone($) {
 # Rückkherfunktion zur Ergebnisauswertung und Löschen von "RUNNING_SET_READING"
 my ($string) = @_;
 my @a    = split("\\|",$string);
 my $hash = $defs{$a[0]};
 my $name = $hash->{NAME};
 my $val  = decode_base64($a[1]);
 my $destdev  = $a[2];
 my $destread = $a[3];
 
 fhem "setreading $destdev $destread $val";
 delete $hash->{HELPER}{RUNNING_SET_READING};
 
return;
}

Der oben gezeigte Code stellt die einfachste Form der BlockingCall-Implementierung dar. Weitere Informationen dazu sind im weiter oben angegebenen Link zum BlockingCall-Wiki enthalten.

Um non-blocking Funktion aufzurufen, ist das bereits beschriebene AT-Device wie folgt zu ändern:

define set.Dum.Rep_EnergyTodayNbl at +*00:01:00 { DumRepEnergyToday("Rep.Energy","MySTP_5000","etoday","Dum.Rep","EnergyToday") }

Der aufgerufenen Funktion "DumRepEnergyToday" werden hierbei der Name des abzufragenden DbRep-Devices, der Name des auszuwertenden Devices, der Name des auszuwertenden Readings sowie das Ziel-Device und das Ziel-Reading als Argumente mitgegeben. Dieser Aufruf kann dadurch universell zum Abruf und Setzen verschiedener auszuwertender Device/Reading-Kombinationen verwendet werden.

Ein Userreading anlegen und für stateformat verwenden

Eine Besonderheit beim Modul DbRep besteht darin, dass sich die Namen der generierten Readings ändern können. Dadurch kann man für die Erstellung eines eigenen Userreading nicht einfach den Namen eines erstellten Readings, z.B. in der Funktion ReadingsVal, verwenden um dessen Wert zu ermitteln.

Eine Möglichkeit besteht darin eine Funktion in 99_myUtils anzulegen und mit dem Attribut "userExitFn" diese Funktion zu aktivieren/zu verwenden.

Für das Beispiel soll ein Reading "power_max" für den erreichten Maximalwert im Monat und der Timestamp des Maximalwertes aus einem Beispielreading "2018-06-01_13-23-02__STP_5000__total_pac__MAX__no_aggregation" (Name kann sich durch das jeweilige Datum ändern) extrahiert werden.

Zunächst wird die Funktion "calcpomax" in 99_myUtils angelegt.

############################################################################################################
########           power_max in DbRep erstellen   
############################################################################################################
sub calcpomax {
 my ($name,$reading,$value) = @_;
 my $hash = $defs{$name};
 
 if($reading =~ /^.*total_pac__MAX.*$/) {
     $reading =~ /^(\d+)-(\d+)-(\d+)_(\d+)-(\d+)-(\d+)_.*$/;
     my $pmts = "$3.$2.$1 $4:$5:$6";
     my $fmtDateTime = FmtDateTime(gettimeofday());
     setReadingsVal($hash,"power_max",$value,$fmtDateTime);
     setReadingsVal($hash,"power_max_ts",$pmts,$fmtDateTime);
 }
return;
}

Wie in der Commandref zu DbRep zu lesen ist, werden der im Attribut "userExitFn" hinterlegten Funktion jeweils der Name des aufrufenden Devices, das Reading und dessen Wert übergeben. Mit ein paar Zeilen Code wird der übergebene Readingsname auf das Vorhandensein des statischen Namensteils (total_pac__MAX) geprüft und dementsprechend der Readingname "power_max" mit dem Wert angelegt, wenn der Regex auf den Readingnamen matched. Gleiches gilt für das Reading "power_max_ts", welches den Zeitpunkt des erreichten Maximalwertes enthalten soll.

Die Aktivierung der Schnittstelle erfolgt im DbRep-Device mit

attr <name> userExitFn calcpomax .*:.*

Mit jedem Lauf von

set <name> maxvalue

wird nun das neue Reading mit angelegt und kann z.B. in einem stateformat verwendet werden:

Rep.powmax.PNG
attr <name> stateformat
{  
 if(ReadingsVal($name,"power_max",0)) {
   (ReadingsVal($name,"power_max",0)*1000)." Watt (erreicht am ".ReadingsVal($name,"power_max_ts","").")";
 } else {
   ReadingsVal($name,"state","done");
 }
}

Nachfolgend noch die Definition des verwendeten DbRep-Devices für diese Auswertung:

defmod Rep.total_pac.currmonth DbRep LogDB
attr Rep.total_pac.currmonth aggregation no
attr Rep.total_pac.currmonth alias Peak WR-Leistung aktueller Monat
attr Rep.total_pac.currmonth allowDeletion 0
attr Rep.total_pac.currmonth devStateIcon connected:10px-kreis-gelb .*disconnect:10px-kreis-rot .*done:10px-kreis-gruen
attr Rep.total_pac.currmonth device STP_5000
attr Rep.total_pac.currmonth disable 0
attr Rep.total_pac.currmonth event-on-update-reading state
attr Rep.total_pac.currmonth group SMA Inverter
attr Rep.total_pac.currmonth reading total_pac
attr Rep.total_pac.currmonth room Energie
attr Rep.total_pac.currmonth showproctime 1
attr Rep.total_pac.currmonth stateFormat {  \
 if(ReadingsVal($name,"power_max",0)) {\
   (ReadingsVal($name,"power_max",0)*1000)." Watt (erreicht am ".ReadingsVal($name,"power_max_ts","").")";;\
 } else {\
   ReadingsVal($name,"state","done");;\
 }\
}
attr Rep.total_pac.currmonth timestamp_begin current_month_begin
attr Rep.total_pac.currmonth timestamp_end current_month_end
attr Rep.total_pac.currmonth userExitFn calcpomax .*:.*
attr Rep.total_pac.currmonth verbose 2


datenbankgestützte Erstellung der Energiebilanz einer SMA PV-Anlage mit Überschußeinspeisung

Hier geht's zum Beitrag.



DbRep-Kommandos regelmäßig mit einem at-Device ausführen

Sollen DbRep-Kommandos regelmäßig zu bestimmten Zeiten bzw. Intervallen ausgeführt werden, kann ein at-Device (alternativ z.B. DOIF) dafür definiert und verwendet werden.

Als Beispiel soll die regelmäßige Ausführung des SQL-Statements

select device, count(*) from history group by DEVICE

über das Set-Kommando sqlCmd dienen.

Zunächst wird das DbRep-Device definiert welches zur Ausführung des Kommandos dienen soll.

define Rep.LogDB.sqlResult DbRep LogDB
attr Rep.LogDB.sqlResult devStateIcon initialized:control_3dot_hor_s connected:10px-kreis-gelb .*disconnect:10px-kreis-rot .*done:10px-kreis-gruen
attr Rep.LogDB.sqlResult event-on-update-reading state
attr Rep.LogDB.sqlResult fastStart 1
attr Rep.LogDB.sqlResult room Datenbank
attr Rep.LogDB.sqlResult showproctime 1
attr Rep.LogDB.sqlResult sqlResultFormat table
attr Rep.LogDB.sqlResult verbose 2


Die Definition Syntax wurde bereits weiter oben erläutert. Das Attribut sqlResultFormat legt fest in welcher Art und Weise das Ergebnis präsentiert werden soll. Auch andere Formate wie JSON sind implementiert.

Um das oben angegbene SQL-Statement zum Beispiel alle 6 Stunden 20 Minuten auszuführen, dient die folgende at-Definition:

define At.LogDB.sqlResult at +*06:20:00 set Rep.LogDB.sqlResult sqlCmd select device, count(*) from history group by DEVICE
attr At.LogDB.sqlResult icon clock
attr At.LogDB.sqlResult room Datenbank->Produktiv


Weitere Informationen sind in der at-CommandRef beschrieben.

Natürlich sind alle verfügbaren DbRep Set/Get-Befehle über diesen Weg ausführbar. Es wird empfohlen das definierte DbRep-Device nur für Auswertungsaufgaben zu verwenden die eine gleiche Attributierung des Devices verlangen. Für weitere Aufgaben sollten weitere DbRep-Devices definiert und entsprechend eingestellt werden.



DbRep Chain - die Kommandokette

Für regelmäßig auszuführende Aufgaben erstellt man sich je ein separates DbRep-Device und setzt die Attribute wie es für die spezifische Aufgabe erforderlich ist. Zu diesem Zweck kopiert man sich einfach ein vorhandenes DbRep-Device mit dem FHEM copy Kommando und passt es danach entsprechend an. Es können beliebig viele DbRep-Devices angelegt werden.

Häufig besteht der Wunsch, verschiedene Datenbankaktionen nacheinander ablaufen zu lassen, z.B. erst minValue writeToDB danach maxValue writeToDB sowie averageValue writeToDB nacheinander auszuführen. Eine solche Verkettung kann durch die Nutzung des executeAfterProc Attributes erstellt werden.

Für das Beispiel werden 3 DbRep-Devices angelegt:

* Rep_Min
* Rep_Max
* Rep_Avg

Das erste Device (Rep_Min) soll für minValue writeToDB genutzt werden. In diesem Device wird das Attribut gesetzt:


attr Rep_Min executeAfterProc set Rep_Max maxValue writeToDB


Dieses Attribut zeigt auf das zweite Device (Rep_Max), welche für maxValue writeToDB verwendet werden soll. In diesem Device wird wiederum das Attribut gesetzt:


attr Rep_Min executeAfterProc set Rep_Avg averageValue writeToDB


Die gesamte Chain startet mit einem at-Device, notify oder dergleichen mit dem Kommando:


 set Rep_Min minValue writeToDB


Funktionsweise:
Die Attribute executeBeforeProc und executeAfterProc führen jeweils das in ihnen hinterlegte FHEM-Kommando oder Perl-Funktion vor bzw. nach der Ausführung des gestarteten DbRep-Kommandos aus.
In dem dargestellten Beispiel startet das at-Device mit

 set Rep_Min minValue writeToDB

die Funktion minValue writeToDB im Device Rep_Min. Wenn das DbRep Rep_Min mit seiner Aufgabe fertig ist, startet es automatisch über executeAfterProc das DbRep Rep_Max die Ausführung von maxValue writeToDB. Hat DbRep Rep_Maxseine Aufgabe beendet, startet es wiederum im letzten Device Rep_Avg das Kommando averageValue writeToDB.
Mit diesem Verfahren läuft immer nur ein Kommando der Reihe nach über die Datenbank.

Natürlich kann diese Chain beliebig erweitert werden, oder im ersten DbRep vor dem Start der Chain das angeschlossene DbLog-Device pausiert werden mit:

attr Rep_Min executeBeforeProc set <DbLog-Device> reopen 7200

Die Beendigung der Pause des DbLog kann das letzte DbRep Rep_Avg übernehmen durch Setzen des Attributes:

attr Rep_Avg executeAfterProc set <DbLog-Device> reopen



Abarbeitung einer sequentiellen Befehlskette mittels non-blocking sqlCmd-Kommandos

(Hinweis: In der aktuellen DbRep-Version kann die seqientielle Kommandoabarbeitung über die Chain-Funktionalität realisiert werden.)


Ein Vorteil von DbRep ist die nicht blockierende Arbeitsweise fast aller DbRep-Befehle (auf Ausnahmen wird in der Commandref hingewiesen). Daraus ergibt sich aber auch das Merkmal, dass nach dem Funktionsaufruf nicht auf das Ergebnis gewartet wird und die FHEM-Schleife unmittelbar nach dem Start des Befehls weiterläuft. Um mehrere voneinander abhängige Befehle, die eine festgelegte Reihenfolge erfüllen müssen, abzuarbeiten, kann die nachfolgend beschriebene Technik zur Kettenbildung angewendet werden. Diese Technik wird anhand einer einfachen Demo erläutert.

Das konstruierte Ziel der Kette ist folgendes:

  • Es soll nacheinander der Insert eines Datensatzes durchgeführt, danach ein Select dieses Datensatzes, ein Update auf ein Feld und zuletzt ein delete dieses Datensatzes in dieser Reihenfolge ausgeführt werden

Demnach sich insgesamt vier SQL-Befehle nacheinander auszuführen (Insert, Select, Update, Delete). Zunächst werden vier identische DbRep-Devices angelegt, die sich lediglich durch ihren Namen unterscheiden. Das wären die Devices:

Rep.insert, Rep.select, Rep.update, Rep.delete

Zur Anlage kann ein DbRep-Device als Vorlage erstellt und nachfolgend dreimal kopiert werden.

define Rep.insert DbRep LogDB
attr Rep.insert allowDeletion 1
attr Rep.insert showproctime 1
attr Rep.insert userExitFn chain

copy Rep.insert Rep.select
copy Rep.insert Rep.update
copy Rep.insert Rep.delete

Das wichtige Detail dieser Devices ist das gesetzte Attribut "userExitFn = chain". Dieses Attribut aktiviert eine Schnittstelle zum Aufruf von benutzereigenem Code nach dem Abschluß eines Datenbank-Calls (siehe Commandref).

Der benutzereigene Code wird nach folgendem Schema in die 99_myUtils.pm eingebaut:

#############################################################################################
#  sqlCmd Commandchain zur sequentiellen Abarbeitung von non-blocking DbRep-Befehlen
#
#  genutzte werden vier DbRep-Devices:  Rep.insert, Rep.select, Rep.update, Rep.delete
#       
#############################################################################################
sub chain {
 my ($name,$reading,$value) = @_;
 my $hash   = $defs{$name};
 my $device = "Testdevice";
 my $model  = "Testmodel";
 my $rc;
 
 if ($name eq "Rep.insert" && $reading eq "chainstart") {
     # Start der 1. Operation (insert)
     CommandSet(undef,"Rep.insert sqlCmd insert into history (DEVICE, TYPE, EVENT, READING, VALUE, UNIT)
	   values (\"$device\", \"$model\", \"definition\", \"DEF\", \"Hugo\", \"Emma\")");
     Log3 $name, 1, "$name - Start chain with insert";
     return;   
 }
 
 if($reading eq "state" && $value eq "done") {
     if ($name eq "Rep.insert") {
         # Auswertung der 1. Operation (insert)
         $rc = ReadingsVal($name, "SqlResultRow_1", "");
         Log3 $name, 1, "$name - number of inserts: $rc";
         
         # Start der 2. Operation (select)
         CommandSet(undef,"Rep.select sqlCmd select VALUE from history where device=\"$device\" and READING=\"DEF\" LIMIT 1;");
     }
   
     if ($name eq "Rep.select") {
         # Auswertung der 2. Operation (select)
         $rc = ReadingsVal($name, "SqlResultRow_1", "");
         Log3 $name, 1, "$name - return of select: $rc";
         
         # Start der 3. Operation (update)
         CommandSet(undef,"Rep.update sqlCmd update history set UNIT=\"neu\" where DEVICE=\"$device\";");
     }
     
     if ($name eq "Rep.update") {
         # Auswertung der 3. Operation (update)
         $rc = ReadingsVal($name, "SqlResultRow_1", "");
         Log3 $name, 1, "$name - number of updates: $rc";
         
         # Start der 4. Operation (delete)
         CommandSet(undef,"Rep.delete sqlCmd delete from history where device = \"$device\" and UNIT = \"neu\";");
     }
     
     if ($name eq "Rep.delete") {
         # Auswertung der 4. Operation (delete)
         $rc = ReadingsVal($name, "SqlResultRow_1", "");
         Log3 $name, 1, "$name - number of deletes: $rc";
     } 
 }
 
return;
}

Die gesamte Abarbeitung wird gestartet mit dem Aufruf:

chain("Rep.insert", "chainstart")

bzw. in der Kommandozeile im FHEMWEB mit:

{chain("Rep.insert", "chainstart")}

Nach dem initialen Aufruf des Insert wird die sub "chain" beendet und nach dem insert-Befehl durch das Device "Rep.insert" wieder aufgerufen. Dabei wird diese sub nach jedem erstellten Reading aufgerufen und dabei neben dem eigenen Devicenamen auch der Readingname sowie dessen Wert zur Auswertung übergeben. Durch die if-Zweige wird bei jedem Durchlauf der richtige Entrypoint für den nachfolgenden Funktionsaufruf ermittelt und ausgeführt.

Nach der Abarbeitung aller vorgesehenen SQL-Statements ist die erfolgreiche Chainverkettung anhand der Ergebnisse im Logfile ersichtlich:

2018.10.24 23:57:37.602 1: Rep.insert - Start chain with insert
2018.10.24 23:57:37.815 1: Rep.insert - number of inserts: 1
2018.10.24 23:57:37.901 1: Rep.select - return of select: Hugo
2018.10.24 23:57:38.033 1: Rep.update - number of updates: 1
2018.10.24 23:57:38.176 1: Rep.delete - number of deletes: 1

Summe aller Einschaltzeiten eines Gerätes

Hier geht's zum Beitrag.


Eine Device spezifische Aufbewahrungszeit (Retention Time) in der Datenbank realisieren

In der DbLog Datenbank gespeicherte Daten müssen bzw. sollten regelmäßig gelöscht werden sofern sie nicht mehr für Auswertungen verwendet werden. Über DbRep Kommandos gibt es dafür viele Möglichkeiten.

Vorgestellt wird hier die Möglichkeit, bereits beim Logging des Events mittels Attribut eine Device spezifische Aufbewahrungszeit festzulegen. Diese Möglichkeit ist ab der DbLog Version 5.9.4 gegeben. Die einzelnen Schritte zur Umsetzung werden nachfolgend beschrieben.

  • im global Device das userattr retentionPeriod definieren. Der Name des Attributes ist natürlich frei wählbar. Das Attribut steht in allen Devices zur Verfügung und bestimmt die Aufbewahrungszeit in Sekunden wenn im Device oder im DbLog Device gesetzt.
 
attr global userattr retentionPeriod 
  • Für die Speicherung des Verfallzeitpunktes in der Datenbank wird die Tabellenspalte EVENT in der Tabelle history verwendet. Normalerweise wird der komplette zu loggende Event in dieser Spalte gespeichert, was im Allgemeinen nicht benötigt wird. Dazu wird in dem relevanten DbLog Device das Attribut valueFn gesetzt um in jedem zu loggenden Datensatz die Spalte EVENT die spezifische Verfallszeit zu speichern:
 
{ 
  my $def = AttrVal ($NAME, 'retentionPeriod',  9999999999);
  $EVENT  = int(time) + AttrVal ($DEVICE, 'retentionPeriod',  $def);
} 
  • Im gleichen DbLog Device wird das Attribut retentionPeriod auf einen Default Wert gesetzt der gelten soll, wenn im zu loggenden Datensatz kein retentionPeriod-Attribut im Quellen-Device hinterlegt ist. Wenn kein retentionPeriod-Attribut im DbLog Device gesetzt ist, erfolgt mit dem Wert 'int(time) + 9999999999' de facto eine zeitlich unbegrenzte Speicherung.
 
attr <DbLog> retentionPeriod 7776000    (Beispiel für Haltezeit von 90 Tagen = 7776000 Sekunden)
Screenshot 2024-01-03 210126.png
  • In jedem relevanten Device wird nun die gewünschte Aufbewahrungszeit in Sekunden gesetzt:
 
attr <Device> retentionPeriod xxxxxxxxx

Im Cache des DbLog Devices findet man jetzt in jedem Datensatz den Verfallszeitpunkt des Datensatzes. Er wird in der EVENT-Spalte der Tabelle gespeichert.

Vor den nächsten Schritten ist sicherzustellen, dass EVENT bei bereits vorhandenen Datensätzen auf einen passenden Verfallszeitpunkt upgedated werden. Dazu eignet sich sqlCmd im DbRep oder ein SQL Editor deiner Wahl:

UPDATE history SET TIMESTAMP=TIMESTAMP, EVENT="1704353074";    (setzt den Verfall auf  04.01.2024 08:24:34)

Damit die Datenlöschungen über eine Auswertung von EVENT später performant ausgeführt werden, sollte ein ensprechender Index angelegt werden und der Datentyp von EVENT in Integer geändert werden. Beide SQL Statements können mit einem DbRep-Device mit gesetzten adminCredentials über "set ... sqlCmd" ausgeführt werden:

CREATE INDEX Retention_Idx ON `history` (EVENT);
ALTER TABLE history MODIFY COLUMN EVENT INT(15); 

Die veränderte Spaltenbreite von EVENT wird im DbLog-Device nachgezogen und das Attribut colEvent gesetzt:

attr <DbLog> colEvent 15

Zur regelmäßigen Löschung der Datensätze mit einer abgelaufenen Aufbewahrungszeit dient ein at-Device in Verbindung mit einem DbRep-Device (z.B. Rep.fhemtest1) welches täglich abgelaufene Datensätze sucht und löscht. Die Definition des at-Devices:

define At.RetentionDel at *23:15:00 {
  my $t = int(time);
  fhem "set Rep.fhemtest1 sqlCmd delete from history where EVENT<'$t'";
}

Das DbRep-Device benötigt keine besonderen Einstellungen außer einem gesetzten Attribut allowDeletion=1.

define Rep.fhemtest1 DbRep <DbLog-Device>
attr Rep.fhemtest1 allowDeletion 1
attr Rep.fhemtest1 event-on-update-reading state
attr Rep.fhemtest1 room DbLog
attr Rep.fhemtest1 showproctime 1



Welche Device/Reading-Kombinationen gibt es in der Datenbank ?

Hinweis: Im aktuellen DbRep-Release kann die nachfolgend beschriebene Auswertung alternativ durch die eingebauten Kommmandos:

set <DbRep-Device> sqlSpecial allDevCount
set <DbRep-Device> sqlSpecial allDevReadCount

ausgeführt werden.

Wenn eine Datenbank längere Zeit gelaufen ist und die zu loggenden Devices bzw. Readings immer mal wieder geändert wurden, wächst der Wunsch einen Überblick über die in der DB enthaltenen Devices/Reading-Kombinationen zu erhalten. Dadurch können wiederum gezielt Auswertungen auf eine bestimmte Device/Reading-Kombination durchgeführt werden.

Um dieses Ziel zu erreichen, wird ein DbRep-Device erstellt welches für die Verwendung eines User-Command eingerichtet wird. Je nach dem gewünschten Ergebnis wird eines der nachfolgenden SQL-Kommandos verwendet.

Selektion aller Device/Reading-Kombinationen in der Datenbank:

select device, reading, count(*) from history group by DEVICE, READING

Nur die geloggten Devices in der Datenbank reporten:

select device, count(*) from history group by DEVICE

Zur Einrichtung wird zunächst das DbRep-Device definiert:

define Rep.LogDB.sqlResult DbRep LogDB

Dabei ist "LogDB" nicht die Datenbank, sondern das DbLog-Device mit dem das DbRep-Device assoziiert und derüber mit der Datenbank verbunden wird !

Um die Ausgabe des späteren Ergebnisses zu formatieren wird das Attribut "sqlResultFormat" auf "table" gesetzt. Dadurch wird das Selektergebnis als Tabelle mit den Spalten Device, Reading und der Anzahl der enthaltenen Datensätze der jeweiligen Kombination erzeugt.

attr Rep.LogDB.sqlResult sqlResultFormat table

Natürlich kann auch ein anderes Ausgabeformat gewählt werden wenn gewünscht oder benötigt. Bei großen Ergebnismengen ist zur Erhöhung der Übersichtlichkeit das Attribut "sqlResultFormat = separated" wahrscheinlich eher geeignet.

Das Attribut stateFormat wird entsprechend gesetzt um nach einem erfolgten Selektionslauf die erzeugte Tabelle in der Raumansicht bzw. im DeviceOverview anzuzeigen:

 attr Rep.LogDB.sqlResult stateFormat { if (defined($defs{$name}{READINGS}{SqlResult})) {
                                            ReadingsVal($name,"SqlResult",undef);
                                        } else {
                                            ReadingsVal($name,"state",undef);
                                        } 
                                      }                                        

Damit ist das DbRep-Device vorbereitet und der Auswertungslauf kann gestartet werden mit:

set Rep.LogDB.sqlResult sqlCmd select device, reading, count(*) from history group by DEVICE, READING;

bzw. wenn nur die in der Datenbank enthaltenen Devices zu erhalten:

set Rep.LogDB.sqlResult sqlCmd select device, count(*) from history group by DEVICE;
set Rep.LogDB.sqlResult sqlCmd select device, count(*) from history group by DEVICE;

Nebenstehender Screenshot zeigt das Ergebnis der Auswertung aller in der Datenbank enthalteten Devices und deren Anzahl von Datensätzen.

Zum leichteren Nachnutzung hier noch die Raw-Definition des fertigen DbRep-Devices:

defmod Rep.LogDB.sqlResult DbRep LogDB
attr Rep.LogDB.sqlResult aggregation no
attr Rep.LogDB.sqlResult comment Device zum freien Report mit sqlCmd.\
- Auswahl von Statements -\
\
Device/Reading-Kombinationen in der Datenbank:\
select device, reading, count(*) from history group by DEVICE, READING\
\
geloggte Devices in der Datenbank:\
select device, count(*) from history group by DEVICE\

attr Rep.LogDB.sqlResult devStateIcon connected:10px-kreis-gelb .*disconnect:10px-kreis-rot .*done:10px-kreis-gruen
attr Rep.LogDB.sqlResult disable 0
attr Rep.LogDB.sqlResult event-on-update-reading state
attr Rep.LogDB.sqlResult group Datenbankauswertungen
attr Rep.LogDB.sqlResult room DbLog
attr Rep.LogDB.sqlResult showproctime 1
attr Rep.LogDB.sqlResult sqlResultFormat table
attr Rep.LogDB.sqlResult stateFormat { if (defined($defs{$name}{READINGS}{SqlResult})) {\
   ReadingsVal($name,"SqlResult",undef);;\
  } else {\
   ReadingsVal($name,"state",undef);;\
  } \
}
attr Rep.LogDB.sqlResult timeout 1000
attr Rep.LogDB.sqlResult verbose 2

Die current-Tabelle mit einem Extrakt der history-Tabelle füllen (tableCurrentFillup)

Diese Funktion erweitert die Möglichkeiten der Verwendung der current-Tabelle des DbLog-Devices. In den meisten Fällen wird im DbLog das Attribut "DbLogType = Current/History" gesetzt sein um bei der Erstellung von SVG's entsprechende Vorschläge in der DropDown-Liste des Editors zur Verfügung zu haben.

Bei dieser Betriebsart werden in der current-Tabelle neu auftretende Events von Device/Reading-Kombinationen eingetragen, sofern sie noch nicht enthalten sind. Werden neue Geräte definiert, erscheinen deren Events in der current-Tabelle, auch wenn man später nicht benötigte Readings mit den Attributen "event-on-change-reading" oder "event-on-update-reading" ausgrenzt. Die einmal gespeicherten Werte bleiben in der current Tabelle erhalten. Sicherlich wird deswegen der eine oder andere User den Inhalt der current Tabelle löschen um wieder eine leere und saubere current-Tabelle zu haben. Das führt dann natürlich dazu, dass nur die ab diesem Zeitpunkt auftretenden Events in der Vorschlagsliste aufgenommen werden und nur selten auftretende Device/Reading-Kombinationen erst nach längerer Zeit wieder in der Tabelle current gespeichert werden.

Darüber hinaus möchte man vielleicht nur für SVG-Diagramme relevante Device/Reading-Einträge in der Vorschagsliste vorfinden oder dort nur Einträge vorhalten, die in den letzten drei Monaten aufgetreten sind. Die Beweggründe können sehr vielfältig sein. Um diesem Wunsch zu entsprechen, gibt es im DbRep die Funktion "set <DbRep-Device> tableCurrentFillup", die eng mit dem dem DbLog-Attribut "DbLogType = SampleFill/History" zusammenarbeitet.

Nachfolgend wird an einem Beispiel erläutert, welche Konfigurationen vorgenommen werden können, um aus der history-Tabelle alle vorhandenen Device/Reading-Kombinationen zu extrahieren und sie zur Verwendung als SVG-Vorschlagsliste in die current-Tabelle einzufügen.

Für das Beispiel wird folgendes angenommen:

  • es gibt ein definiertes DbLog-Device "LogDB" welches in die Datenbank "fhem" loggt
  • das DbRep-Device zur Ausführung der Funktion wird "Rep.FillCurr.fhem" genannt
  • es sollen alle in der history-Tabelle vorkommenden Device/Reading-Kombinationen extrahiert und in die current-Tabelle eingetragen werden (Einschränkungen auf bestimmte Devices / Readings oder zeitliche Abgrenzungen werden nicht vorgenommen, aber es wird darauf hingewiesen, wie es gemacht werden kann)


a) Anlegen des DbRep-Devices

Die Definition des DbRep-Devices erfolgt unter Angabe des zu verbindenden DbLog-Devices (nicht der Datenbank selbst):

define Rep.FillCurr.fhem DbRep LogDB

In dem so definierten neuen Device sind die Attribute zu setzen, die für die beabsichtigte Funktion wichtig sind. Da dieses Device auch für die Löschung der in der current-Tabelle gespeicherten Datensätze verwendet wird, muss das Attribut "allowDeletion = 1" gesetzt werden.

attr Rep.FillCurr.fhem allowDeletion 1

Ebenfalls wichtig ist die Erzeugung eines Events sobald der Löschvorgang der current-Tabelle abgeschlossen ist. Dazu wird im Beispiel "event-on-update-reading" verwendet:

attr Rep.FillCurr.fhem event-on-update-reading state,--DELETED_ROWS_CURRENT-- 
set Rep.FillCurr.fhem tableCurrentPurge

Für die Funktion nicht wichtig, aber durchaus interessant zu wissen wie lange die Laufzeit der Funktionen ist, wird noch das Attribut "showproctime = 1" gesetzt. Damit werden die Readings "background_processing_time" und "sql_processing_time" erzeugt welche die jeweilige Bearbeitungszeit in Sekunden darstellen.

attr Rep.FillCurr.fhem showproctime 1

Mit dem so vorbereiteten Device können schon die Funktionen "tableCurrentPurge" und "tableCurrentFillup" manuell getestet werden. Mit dem Kommando:

set Rep.FillCurr.fhem tableCurrentPurge

wird der gesamte aktuelle Inhalt der current-Tabelle gelöscht. Wie im nebenstehenden Screenshot sichtbar, wurden in dem Beispiel 170 Datensätze gelöscht und dazu rund 30ms benötigt. Mit Abschluß der Operation wird der Delete-Event erzeugt:

2018-01-05 13:38:14.596 DbRep Rep.FillCurr.fhem --DELETED_ROWS_CURRENT--: 170
set Rep.FillCurr.fhem tableCurrentFillup

Dieser Event wird später in einem Notify für die automatisierte Current-Auffüllung verwendet. Eine Zählug der current-EInträge mit "set Rep.FillCurr.fhem countEntries current" ergibt "-", d.h. der Löschbefehl war erfolgreich. Im jetzigen Zustand würde bei der Erstellung eines SVG keine DropDown-Liste angezeigt werden und statt dessen die Felder im SVG-Editor frei bearbeitbar sein, weil die current-Tabelle keine Werte enthält. Gleichermaßen kann bereits die Auffüllung der current-Tabelle getestet werden mit dem Befehl:

set Rep.FillCurr.fhem tableCurrentFillup

Je nach Größe der Datenbank kann diese Operation einige Zeit in Anspruch nehmen. Da dieser Befehl wie bei DbRep üblich non-blocking abgearbeitet wird, hat die Laufzeit keinen Einfluss auf die sonstige FHEM-Verfügbarkeit.

set Rep.FillCurr.fhem tableCurrentFillup

Wie bereits erwähnt, wird in dem Beispiel die gesamte history-Tabelle ausgewertet. Sollen zum Beispiel nur die letzten 90 Tage zur Auswertung herangezogen werden, kann das Attribut "timeDiffToNow = d:90" gesetzt werden.

Weitere Eingrenzungen bzgl. der auszuwertenden Devices bzw. Readings können über die Attribute "device" und "reading" vorgenommen werden. Die Syntax für die genannten Attribute und deren Auswirkung entnehme bitte der commandref/DbRepattr.

In unserem Beispiel wurde die current-Tabelle mit 170 Datensätzen aufgefüllt. Dazu wurden 291 Sekunden benötigt (die Tabelle history enthält insgesamt rund 11 Mio. Einträge)

Wird jetzt ein neues SVG erstellt, erscheinen alle in selektierten Device/Reading-Kombinationen alphabetisch sortiert als DopDown-Liste im SVG-Editor.

b) Konfiguration des DbLog-Devices

In dem bestehen DbLog-Device "LogDB" ist zur Vorbereitung lediglich das Attribut "DbLogType = SampleFill/History" zu setzen:

attr LogDB DbLogType SampleFill/History

Entgegen der Arebeitsweise mit "Current/History" wird die current-Tabelle nicht mehr durch die Logging-Engine aktiv mit jedem relevanten Event gefüllt, sondern kann durch externe Tools, in dem Fall dem DbRep-Device, beschrieben werden. Die current-Tabelle wird aber, ob leer oder gefüllt, bei der Erstellung eines SVG-Devices ausgewertet und in Abhängigeit des Ergebnisses eine DropDown-Liste zur Verfügung gestellt, oder frei editierbare Felder angeboten falls die current-Tabelle leer ist.

c) Definition der at/notify-Devices zum automatisierten Ablauf

Die Löschung des Inhaltes der current-Tabelle und die erneute Ausffüllung soll natürlich autmatisiert regelmäßig erfolgen. Zu diesem Zweck wird ein at-Device (At.LogDB.currentPurge) und ein notify-Device angelegt. Das AT-Device soll alle 8 Stunden und 15 Minuten das Löschen des current-Inhaltes steuern und nach Abschluß der Löschung soll getriggert durch das "--DELETED_ROWS_CURRENT--"-Event die Neubefüllung der current-Tabelle veranlasst werden.

Zur Definition dieser Devices gibt es nicht viel zu erläutern. Die entsprechende Syntax kann bei at bzw. notify in der commandref nachgelesen werden.

at-Device:

define At.LogDB.currentPurge at +*08:15:00 set Rep.FillCurr.fhem tableCurrentPurge

notify-Device:

define N.LogDB.Fillup notify Rep.FillCurr.fhem:.*DELETED_ROWS_CURRENT.*  sleep 5;set Rep.FillCurr.fhem tableCurrentFillup

Das nichtblockierende FHEM-sleep (sleep gefolgt von einem weiteren Befehl) wird lediglich dazu verwendet, um zwischen der Delete- und Auffüll-Operation eine kleine Pause einzufügen.

d) Arbeitsweise und Raw-Definitionen

Durch das "At.LogDB.currentPurge"-Device wird alle 8 Stunden und 15 Minuten ein Löschlauf der current-Tabelle gestartet. Dadurch wird dem darauf folgenden Auffüllprozess immer eine saubere leere current-Tabelle zur Verfügung gestellt. Nach dem Löschlauf wird durch das "N.LogDB.Fillup"-Device, getriggert durch den Event "Rep.FillCurr.fhem:.*DELETED_ROWS_CURRENT.*", die erneute Auffüllung der current-Tabelle gestartet. Die Daten werden aus der histrory-Tabelle gelesen, wobei der Zeitraum der Selektion und die zu betrachtenden Devices / Readings durch die Zeit-Attribute bzw. device / reading-Attribute des DbRep-Devices "Rep.FillCurr.fhem" individuell beeinflußt werden können.

Hilfreich ist es auch, wenn das DbLog-Device "LogDB" im asynchronen Modus betrieben wird.

Abschließend sind hier noch die Raw-Definitionen der beteiligten Devices (außer LogDB) angehängt, um das Beispiel leicht nachvollziehbar zu gestalten.

DbRep-Device "Rep.FillCurr.fhem":

defmod Rep.FillCurr.fhem DbRep LogDB
attr Rep.FillCurr.fhem allowDeletion 1
attr Rep.FillCurr.fhem comment Current Fillup für DB fhem
attr Rep.FillCurr.fhem devStateIcon connected:10px-kreis-gelb .*disconnect:10px-kreis-rot .*done:10px-kreis-gruen
attr Rep.FillCurr.fhem event-on-update-reading state,--DELETED_ROWS_CURRENT--
attr Rep.FillCurr.fhem icon icoTool
attr Rep.FillCurr.fhem room DbLog
attr Rep.FillCurr.fhem showproctime 1
attr Rep.FillCurr.fhem verbose 3

at-Device "At.LogDB.currentPurge":

defmod At.LogDB.currentPurge at +*08:15:00 set Rep.FillCurr.fhem tableCurrentPurge
attr At.LogDB.currentPurge comment Zeitgesteuertes Löschen der Current Tabelle. Nach dem Löschen wird \
Eventgesteuert (Notify N.LogDB.Fillup) ein tableCurrentFillup gestartet.
attr At.LogDB.currentPurge room DbLog

notify-Device "N.LogDB.Fillup":

defmod N.LogDB.Fillup notify Rep.FillCurr.fhem:.*DELETED_ROWS_CURRENT.*  sleep 5;;set Rep.FillCurr.fhem tableCurrentFillup
attr N.LogDB.Fillup disable 1
attr N.LogDB.Fillup room DbLog
attr N.LogDB.Fillup verbose 2

Backup/Restore einer SQLite Datenbank im laufenden Betrieb

In diesem Beitrag wird erläutert, wie man mit DbRep ein Backup der SQLite-Datenbank erstellen kann, welche Möglichkeiten es dabei gibt und wie man eine Datenbank aus einem Backup wieder herstellen kann. Die Backup-Funktion nutzt die SQLite Online Backup API und ermöglicht es, konsistente Backups der SQLite-DB in laufenden Betrieb zu erstellen ohne die Datenbank schließen zu müssen.

Neben dem eigentlichen Backup kann optional einstellt werden, dass:

  • vor dem Backup eine Datenbankoptimierung (Vacuum) ausgeführt werden soll. Dadurch wird Plattenplatz freigegeben sofern möglich.
  • vor und nach dem Backup kann ein FHEM-Kommando oder eine Perl-Routine ausgeführt werden (eine Perl-Routine ist in {} einzuschließen)
  • das erstellte Dumpfile kann über FTP(S) an einen FTP-Server übertragen werden
  • über die interne Versionsverwaltung kann festgelegt werden, wieviele erstellte Backups auf dem Datenträger erhalten bleiben sollen (default: 3)

Für die Erläuterung des Beispiels soll ein DbRep-Device erstellt werden, welches:

  • die SQLite Datenbank im Betrieb (Online) sichert
  • vor dem Dump einen Vacuum-Lauf durchführt
  • das erstellte Dumpfile auf einen FTP-Server überträgt
  • 2 Versionen (Dumpfiles) nach einem erfolgreichen Backuplauf im Dumpverzeichnis verbleiben sollen


1. Anlegen des DbRep-Devices

Das anzulegende Device wird sowohl für das (regelmäßige) Backup als auch für einen eventuellen Restore verwendet. Die Definition des DbRep-Devices erfolgt unter Angabe des zu verbindenden DbLog-Devices (nicht der Datenbank selbst):

define Rep.SQLite DbRep LogSQLITE

Ist das Device definiert, verbindet es sich zu der Datenbank und wechselt in den state "connected", sofern die Verbindung erfolgreich verlief. Prinzipiell funktioniert der hier beschriebene Backup-Prozess mit einem im synchronous Mode betriebenen DbLog-Device, aber es ist dringend angeraten das DbLog-Device (LogSQLITE) in den asynchronen Modus umzuschalten. Dadurch werden Blockierungen von FHEM und Verluste von Daten verhindert.

2. Einstellungen des DbRep-Devices (Attribute)

Für die gewünschte Funktion müssen in diesem neu definierten Device einige relevante Attribute gesetzt werden.


a) dumpDirLocal

Das zu erstellende Backupfile wird per default im log-Verzeichnis ./log (meist /opt/fhem/log) des FHEM-Rechners gespeichert. Um es an einer anderen Stelle zu speichern kann das Attribut "dumpDirLocal" gesetzt werdden. Dieses Verzeichnis kann sich auf dem lokalen Datenträger befinden, oder ein per NFS gemountetes Verzeichnis sein. In jedem Fall muss der User unter dem FHEM läuft Schreibrechte auf dieses Verzeichnis besitzen.

Für das Beispiel sollen die Dump-Files auf einen NFS-Mount einer Synology Diskstation gespeichert werden. Dazu wurde ein Mount in /etc/fstab erstellt um den Pfad "/sds1/backup" lokal verfügbar zu machen:

sds1.<fqdn>:/volume1/ApplicationBackup /sds1/backup nfs auto,defaults,tcp,intr 0 0

In diesem Verzeichnis exsitiert das Directory "dumps_FHEM", in dem die Backup-Files gespeichert werden sollen. Das Attribut wird entsprechend gesetzt auf:

attr Rep.SQLite dumpDirLocal /sds1/backup/dumps_FHEM


b) dumpFilesKeep

Dieser Parameter stellt die Anzahl der aufzubewahrenden Backup-Files ein. Es werden immer die "x" neuesten bzw. letzten Files im Verzeichnis gehalten, wobei nur die zu der jeweiligen Datenbank gehörenden Files betrachtet werden. Die Zugehörigkeit des Dump-Files zur Quelldatenbank wird anhand des Filenamens ermittelt, der sich zusammensetzt aus <DB-Name ohne Endung>_<Erstellungsdatum und Uhrzeit>.sqlitebkp

Beispiel: fhem_2018_01_12_17_15.sqlitebkp

Ist dieses Attribut nicht gesetzt, werden die 3 neuesten Backup-Files im Verzeichnis behalten. Um nur 2 Files zu behalten wird das Attribut gesetzt auf:

attr Rep.SQLite dumpFilesKeep 2


c) Aktionen vor und nach dem Backup ausführen

Vor und nach der Dump-Ausführung kann ein FHEM-Kommando bzw. Perl-Befehle ausgeführt werden. Perl-Befehle müssen in {} eingeschlossen werden. Für das Beispiel soll vor dem Backup die Verbindung des DbLog-Devices LogSQLITE zur Datenbank getrennt werden. Dadurch werden keine neuen Daten in die Datenbank geschrieben. Wenn das DbLog-Device LogSQLITE im asynchronen Modus wie empfohlen betrieben wird, tritt auch kein Datenverlust ein, da die zu loggenden Daten im Cache verbleiben bis die Verbindung zur DB wieder hergestellt wird.

Es sei noch einmal darauf hingewiesen, dass das Backup-Verfahren ebenso im synchronen Modus und ohne das DbLog-Device von der Datenbank zu trennen, funktioniert und nur im Beispiel zur Demonstation der Möglichkeiten dient.

Um die Verbindung zu trennen wird ein "set LogSQLITE reopen <Zeit in Sekunden>" verwendet. Die Zeitspanne wird sehr großzügig gewählt und soll sicherstellen, dass innerhalb dieser Zeit das Backup abgeschlossen ist. Nach dem Backup wird sofort ein "set LogSQLITE reopen" ausgeführt, was die Verbindung des DbLog-Devices LogSQLITE zur Datenbank unmittelbar wieder herstellt..


attr Rep.SQLite executeBeforeProc set LogSQLITE reopen 3600
attr Rep.SQLite executeAfterProc set LogSQLITE reopen


d) vor dem Backup Datenbank verkleinern (vacuum)

Diese optionale Funktion wird durch das Attribut "optimizeTablesBeforeDump" eingeschaltet. Dadurch die Vacuum-Funktion wird Platz innerhalb der Datenbank freigegeben der durch Löschvorgänge entstanden ist und dadurch die Größe des Datenbankfiles verringert.

attr Rep.SQLite optimizeTablesBeforeDump 1


e) das Dump-File nach dem Backup zum FTP-Server übertragen

DbRep bietet die Möglichkeit, das erstellte Dump-File per FTP oder verschlüsselt per FTP(S) zum Server zu übertragen. Dazu gibt es einen Satz von Attributen um dem Device alle notwendigen Angaben für den FTP-Transfer zur Verfügung zu stellen. Es gibt dafür folgende Attribute:

  • ftpUse : FTP Transfer nach dem Dump wird eingeschaltet (bzw. ftpUseSSL mit SSL Verschlüsselung)
  • ftpUser : User zur Anmeldung am FTP-Server, default: anonymous
  • ftpPwd : Passwort des FTP-Users, default nicht gesetzt
  • ftpDir : Verzeichnis auf dem FTP-Server in welches das File übertragen werden soll (default: FTP-root)
  • ftpPort : FTP-Port, default: 21
  • ftpServer : Name oder IP-Adresse des FTP-Servers
  • ftpTimeout : Timeout für die FTP-Verbindung in Sekunden (default: 30)
  • ftpPassive : setzen wenn passives FTP verwendet werden soll
  • ftpDebug : Debugging des FTP Verkehrs zur Fehlersuche

Gemäß dieser Beschreibung und dem Ziel dieses Beispiels werden die für FTP-Transfer relevanten Attribute wie folgt gesetzt:

attr Rep.SQLite ftpDir /ftp                # Subdirectory ftp unter FTP-root als Zielverzeichnis
attr Rep.SQLite ftpPwd ftpftp1
attr Rep.SQLite ftpServer sds1.<fqdn>
attr Rep.SQLite ftpUse 1
attr Rep.SQLite ftpUser ftpuser

Der FTP-Server muss natürlich vorab eingerichtet und funktionsfähig sein. Sollten beim FTP-Transfer Fehler auftreten, kann das Attribut ftpDebug = 1 gesetzt werden um entsprechende Ausgaben zur Analyse des Problems im Log zu erhalten.

Fertig definiertes Device Rep.SQLite
f) Hilfsattribute

Für die Funktion nicht relevant aber informativ ist das Attribut "showproctime = 1". Ist das Attribut gesetzt, wird das Reading "background_processing_time" angelegt. Es enthält nach der Ausführung die verbrauchte Prozesszeit.


Das fertig konfigurierte Device hier als RAW-Definition zur einfachen Nachnutzung. Die Angaben sind natürlich anzupassen:

define Rep.SQLite DbRep LogSQLITE
attr Rep.SQLite devStateIcon connected:10px-kreis-gelb .*disconnect:10px-kreis-rot .*done:10px-kreis-gruen
attr Rep.SQLite dumpDirLocal /sds1/backup/dumps_FHEM
attr Rep.SQLite dumpFilesKeep 2
attr Rep.SQLite event-on-update-reading state
attr Rep.SQLite executeAfterProc set LogSQLITE reopen
attr Rep.SQLite executeBeforeProc set LogSQLITE reopen 3600
attr Rep.SQLite ftpDir /ftp
attr Rep.SQLite ftpPwd ftpftp1
attr Rep.SQLite ftpServer sds1.<fqdn>
attr Rep.SQLite ftpUse 1
attr Rep.SQLite ftpUser ftpuser
attr Rep.SQLite optimizeTablesBeforeDump 1
attr Rep.SQLite room DbLog
attr Rep.SQLite showproctime 1
attr Rep.SQLite verbose 3



3. Backup durchführen

Mit dem wie beschrieben eingerichteten DbRep-Device kann das Buckup gestartet werden mit:

set Rep.SQLite dumpSQLite


LogSQLITE ist geschlossen bis ...

Zu Beginn des Vorgangs wird sofort die Verbindung des DbLog-Devices zur Datenbank getrennt. Ein laufendes Backup kann mit "set Rep.SQLite cancelDump" abgebrochen werden. Das laufende Backup wird im state angezeigt mit "SQLite Dump is running - be patient and see Logfile !". Dieser Satz ist ernst gemeint, wobei der Dump über die Online-API sehr schnell arbeitet. Im Logfile mit (mindestens) verbose 3 werden die relevanten Informationen zur Verfügung gestellt:

2018.01.13 08:35:10.745 3: DbRep Rep.SQLite - ################################################################           
2018.01.13 08:35:10.746 3: DbRep Rep.SQLite - ###                    New SQLite dump                       ###
2018.01.13 08:35:10.747 3: DbRep Rep.SQLite - ################################################################
2018.01.13 08:35:10.748 3: DbRep Rep.SQLite - execute command before dump: 'set LogSQLITE reopen 3600' 
2018.01.13 08:35:10.883 2: DbLog LogSQLITE: Connection closed until 09:35:10 (3600 seconds).
2018.01.13 08:35:10.917 3: DbRep Rep.SQLite - Size of database /opt/fhem/fhem.db before optimize (MB): 2554
2018.01.13 08:35:10.918 3: DbRep Rep.SQLite - VACUUM database /opt/fhem/fhem.db....
2018.01.13 08:44:04.216 3: DbRep Rep.SQLite - Size of database /opt/fhem/fhem.db after optimize (MB): 2554
2018.01.13 08:44:04.280 3: DbRep Rep.SQLite - Starting dump of database 'fhem.db'
2018.01.13 08:45:03.810 3: DbRep Rep.SQLite - Size of backupfile: 2553.64 MB
2018.01.13 08:45:04.579 3: DbRep Rep.SQLite - FTP: transferring /sds1/backup/dumps_FHEM/fhem_2018_01_13_08_44.sqlitebkp
2018.01.13 08:45:48.838 3: DbRep Rep.SQLite - FTP: fhem_2018_01_13_08_44.sqlitebkp transferred successfully to sds1.myds.me into dir /ftp
2018.01.13 08:45:48.844 3: DbRep Rep.SQLite - Deleting old dumpfile 'fhem_2018_01_13_08_13.sqlitebkp' 
2018.01.13 08:45:49.273 3: DbRep Rep.SQLite - Finished backup of database fhem - total time used: 638 seconds
2018.01.13 08:45:49.358 2: DbRep Rep.SQLite - command after dump message: "Reopen executed." 
2018.01.13 08:45:49.370 3: DbRep Rep.SQLite - Database dump finished successfully. 
das Backup läuft

Setzt man das Attribut "ftpDebug=1", erhält man im Log zusätzliche Informationen zum FTP-Transfer:

2018.01.13 08:50:22.385 3: DbRep Rep.SQLite - Size of backupfile: 2553.82 MB
Net::FTP>>> Net::FTP(2.79)
Net::FTP>>>   Exporter(5.72)
Net::FTP>>>   Net::Cmd(2.30)
Net::FTP>>>   IO::Socket::INET(1.35)
Net::FTP>>>     IO::Socket(1.38)
Net::FTP>>>       IO::Handle(1.35)
Net::FTP=GLOB(0xb12ed20)<<< 220 SDS1 FTP server ready.
Net::FTP=GLOB(0xb12ed20)>>> USER ftpuser
Net::FTP=GLOB(0xb12ed20)<<< 331 Password required for ftpuser.
Net::FTP=GLOB(0xb12ed20)>>> PASS ....
Net::FTP=GLOB(0xb12ed20)<<< 230 User ftpuser logged in, access restrictions apply.
Net::FTP=GLOB(0xb12ed20)>>> TYPE I
Net::FTP=GLOB(0xb12ed20)<<< 200 Type set to I.
Net::FTP=GLOB(0xb12ed20)>>> CWD /ftp
Net::FTP=GLOB(0xb12ed20)<<< 250 CWD command successful.
2018.01.13 08:50:22.880 3: DbRep Rep.SQLite - FTP: transferring /sds1/backup/dumps_FHEM/fhem_2018_01_13_08_49.sqlitebkp
Net::FTP=GLOB(0xb12ed20)>>> PORT 192,168,2,45,145,42
Net::FTP=GLOB(0xb12ed20)<<< 200 PORT command successful.
Net::FTP=GLOB(0xb12ed20)>>> ALLO 2677867520
Net::FTP=GLOB(0xb12ed20)<<< 202 ALLO command ignored.
Net::FTP=GLOB(0xb12ed20)>>> STOR fhem_2018_01_13_08_49.sqlitebkp
Net::FTP=GLOB(0xb12ed20)<<< 150 Opening BINARY mode data connection for 'fhem_2018_01_13_08_49.sqlitebkp'.
Net::FTP=GLOB(0xb12ed20)<<< 226 Transfer complete.
2018.01.13 08:51:06.859 3: DbRep Rep.SQLite - FTP: fhem_2018_01_13_08_49.sqlitebkp transferred successfully to sds1.myds.me into dir /ftp
Backup ist erfolgreich abgeschlossen

In vorliegenden Beispiel wird nach einem erfolgreichen Backup im state "Warning - dump finished, but command after dump message appeared" angezeigt. Diese Warnung rührt daher, dass das Reopen-Kommando eine Rückkehrinfo mitteilt, die als Problem gewertet und im Reading "afterdump_message" ausgegeben wird. In diesem Fall ist es nur eine Information.

In den erstellten Readings wird zusammengetragen welches File mit welcher Größe ertsellt wurde, welche alten Files gelöscht wurden, Informationen zum FTP-Transfer und welche Zeit der Gesamtprozess benötgt hat.

Das so vorbereitete Backup kann nun z.B. täglich oder auch mit einer wesentlich kürzeren Wiederholungsperiode (alle x Stunden) über ein at-Device eingeplant werden:

define At.SQLite.Dump at *23:52:00 set Rep.SQLite dumpSQLite

4. Restore

Sollte es einmal zur Korruption des Datenbankfiles kommen (database disk image is malformed) und Reparaturversuche erfolglos bleiben, kann ein Restore die einzigste oder vielleicht auch einfachste Möglichkeit sein die Datenbank wieder herzustellen.

Das kann mit dem angelegten DbRep-Device im laufenden Betrieb geschehen. Auch hier ist wieder wichtig, dass das DbLog-Device LogSQLITE im asynchronen Modus betrieben wird und die Reopen-Zeit (siehe Einstellung Attribut "executeBeforeProc") sehr großzügig eingestellt ist.

Zum Restore gibt es den Befehl "set ... restoreSQLite <File>".

Auswahlmenü Files für Restore

Im FHEMWEB öffnet sich dazu eine DropDown-Liste die alle vorhandenen und zur Quelldatenbank passenden Dumpfiles auflistet. Das herzustellende File

kann so bequem ausgewählt werden. Die Dumpfiles müssen sich in dem Verzeichnis befinden, welches durch das Attribut "dumpDirLocal" festgelegt wurde (default (./log).

Restore läuft

Der restore wird demzufolge gestartet mit:

set Rep.SQLite restoreSQLite <File>


Wieder wird zu Beginn des Vorgangs das verbundene DbLog-Device von der Datenbank abgekoppelt und nach dem Restore wieder automatisch verbunden. Eine Datenbankoptimierung bzw. FTP-Transfer wird bei diesem Vorgang natürlich nicht ausgeführt.


Das Log zeigt den Prozessverlauf:

2018.01.13 09:21:55.435 3: DbRep Rep.SQLite - ################################################################
2018.01.13 09:21:55.437 3: DbRep Rep.SQLite - ###             New database Restore/Recovery                ###
2018.01.13 09:21:55.437 3: DbRep Rep.SQLite - ################################################################
2018.01.13 09:21:55.438 3: DbRep Rep.SQLite - execute command before restore: 'set LogSQLITE reopen 3600' 
2018.01.13 09:21:55.448 2: DbLog LogSQLITE: Connection closed until 10:21:55 (3600 seconds).
2018.01.13 09:21:55.477 3: DbRep Rep.SQLite - Starting restore of database 'fhem.db'
2018.01.13 09:30:12.533 3: DbRep Rep.SQLite - Restore of /sds1/backup/dumps_FHEM/fhem_2018_01_13_08_49.sqlitebkp into 'fhem.db' finished - total time used: 497 seconds.
2018.01.13 09:30:12.685 2: DbRep Rep.SQLite - command after restore message: "Reopen executed." 
2018.01.13 09:30:12.700 3: DbRep Rep.SQLite - Database restore finished successfully.

Der Restore wurde erfolgreich abgeschlossen und die Verbindung des DbLog-Device LogSQLITE zur Datenbank wiederhergestellt. Das Logging wird nahtlos fortgeführt.

Restore erfolgreich abgeschlossen

Es ist natürlich zu beachten, dass die Daten zwischen dem Erstellungszeitpunkt des eingespielten Backups und der aktuellen Zeit verloren sind ! Die bestehende (korrupte) Datenbank wird überschrieben. Deswegen ist es ratsam immer ein aktuelles (zeitnahes) Backup zu haben um den Datenverlust möglichst gering zu halten.

5. Links

Backup/Restore einer MySQL/MariaDB Datenbank im laufenden Betrieb

Das Backup einer MySQL/MariaDB Datenbank (im Folgenden stellvertretend als MySQL bezeichnet) kann als Varianten

  • clientSide
  • serverSide

Beim clientSide Backup werden die Daten über SQL-Statements aus der Datenbank gelesen, auf dem Client (dem FHEM-Server) verarbeitet und das Dumpfile geschrieben. Es werden history- und current-Tabelle gesichert, sowie eventuell weitere angelegte Tabellen und Views. Allerdings benötigt dieser Modus umfangreichere RAM und CPU-Ressorcen auf dem Client.

Die serverSide Option wird nachfolgend beispielhaft genauer erläutert.


serverSide Backup Option

Neben dem eigentlichen Backup kann optional einstellt werden, dass:

  • vor dem Backup eine Tabellenoptimierung (optimizeTables) ausgeführt werden soll. Dadurch wird Plattenplatz freigegeben sofern möglich.
  • vor und nach dem Backup kann ein FHEM-Kommando oder eine Perl-Routine ausgeführt werden (eine Perl-Routine ist in { } einzuschließen)
  • das erstellte Dumpfile kann komprimiert werden
  • das erstellte Dumpfile kann über FTP(S) an einen FTP-Server übertragen werden
  • über die interne Versionsverwaltung kann die Anzahl der im Backupverzeichnis zu verbleibenden Backup-Files festgelegt werden (default: 3)


Für die Erläuterung des Beispiels soll ein DbRep-Device erstellt werden, welches:

  • die MySQL Datenbank im Betrieb (Online) sichert
  • vor dem Dump die history-Tabelle optimiert
  • das erstellte Dumpfile auf einen FTP-Server überträgt
  • drei Versionen (Dumpfiles) nach der Übertragung auf dem FTP-Server belässt, ältere Files werden vom FTP-Server gelöscht
  • eine Version (Dumpfile) nach einem erfolgreichen Backuplauf im Dumpverzeichnis verbleiben sollen



1. Anlegen des DbRep-Devices

Das anzulegende Device wird sowohl für das (regelmäßige) Backup als auch für ein eventuelles Restore verwendet. Die Definition des DbRep-Devices erfolgt unter Angabe des zu verbindenden DbLog-Devices (nicht der Datenbank selbst):

define Rep.fhemtest.Dump.ServerSide DbRep LogDB

Ist das Device definiert, verbindet es sich zu der Datenbank und wechselt in den state "connected", sofern die Verbindung erfolgreich verlief. Prinzipiell funktioniert der hier beschriebene Backup-Prozess mit einem im synchronous Mode betriebenen DbLog-Device, aber es ist dringend angeraten das DbLog-Device (LogDB) in den asynchronen Modus umzuschalten. Dadurch werden FHEM-Blockierungen vermieden.



2. Einstellungen des DbRep-Devices (Attribute)

Für die gewünschte Funktion müssen in diesem neu definierten Device einige relevante Attribute gesetzt werden.


a) dumpDirRemote

Der Dump wird durch den MySQL-Server erstellt und per default im Home-Verzeichnis des MySQL-Servers gespeichert. Es wird die gesamte history-Tabelle (nicht current-Tabelle) im CSV-Format ohne Einschränkungen exportiert.

Um es an einer anderen Stelle zu speichern, wird das Attribut "dumpDirRemote" gesetzt.

Auch wenn der MySQL-Server mit auf dem FHEM-Server läuft, also lokal, wird dieses Attribut zur Veränderung des Zielverzeichnisses verwendet.



b) dumpDirLocal

Soll die interne Versionsverwaltung und die Dumpfilekompression des Moduls genutzt, sowie die Größe des erzeugten Dumpfiles ausgegeben werden, ist das Verzeichnis "dumpDirRemote" des MySQL-Servers auf dem Client zu mounten und im Attribut "dumpDirLocal" dem DbRep-Device bekannt zu machen. Gleiches gilt wenn der FTP-Transfer nach dem Dump genutzt werden soll (Attribut "ftpUse" bzw. "ftpUseSSL").

Im Beispiel läuft der MySQL-Server auf einem entfernten Server und die Dump-Files werden dort im Verzeichnis

/volume1/ApplicationBackup

angelegt. Dieses Verzeichnis soll auf dem FHEM-Server als Verzeichnis "/sds1/backup" gemountet werden. Wenn noch nicht passiert, den NFS Client auf dem FHEM Server installieren:

sudo apt-get update
sudo apt-get install nfs-common

Auf dem entfernten Server wird das Verzeichnis freigegeben/exportiert. Wenn noch nicht vorhanden, ist zunächst das notwendige Paket zu installieren:

sudo apt-get install nfs-kernel-server

Ist das Paket vorhanden, existiert die Datei /etc/exports. In der Datei /etc/exports wird das zu exportierende Verzeichnis hinzugefügt. Aus Gründen der Sicherheit wird nur die IP-Adresse des FHEM Servers für den Zugriff zugelassen:

 /volume1/ApplicationBackup 192.168.50.33(rw,root_squash,async,no_subtree_check)

Soll ein Netzwerk freigegeben werden, kann der Eintrag in in dieser Form erfolgen:

/volume1/ApplicationBackup 192.168.XXX.0/24(rw,root_squash,async,no_subtree_check)

Danach Datei /etc/exports neu einlesen:

sudo exportfs -ra

Auf dem Client Rechner wird der Mountpoint für das einzuhängende Verzeichnus erstellt:

sudo mkdir /sds1/backup

Einen Eintrag in /etc/fstab erstellen:

<IP-Adresse>:/volume1/ApplicationBackup /sds1/backup nfs auto,defaults,tcp,intr 0 0

Ausführen:

mount /sds1/backup

In diesem Verzeichnis existiert das Directory "dumps_FHEM", in dem die Backup-Files gespeichert werden sollen. Das Attribut wird entsprechend gesetzt auf:

attr Rep.fhemtest.Dump.ServerSide dumpDirLocal /sds1/backup/dumps_FHEM


Hinweis:
Läuft der MySQL-Server mit FHEM lokal auf dem gleichen Server, zeigen die Attribute dumpDirRemote und dumpDirLocal auf das identische Verzeichnis. Trotzdem sind beide Attribute zu definieren.



b) dumpFilesKeep

Dieser Parameter stellt die Anzahl der aufzubewahrenden Backup-Files ein. Es werden immer die "x" neuesten bzw. letzten Files im Verzeichnis gehalten, wobei nur die zu der jeweiligen Datenbank gehörenden Files betrachtet werden. Die Zugehörigkeit des Dump-Files zur Quelldatenbank wird anhand des Filenamens ermittelt.

Er setzt sich zusammen aus <Datenbankname>_history_<Erstellungsdatum_Uhrzeit>.csv

Beispiel: fhemtest_history_2020_05_14_03_42.csv (+.gzip falls Dumpfiles komprimiert werden) 

Ist dieses Attribut nicht gesetzt, werden die 3 neuesten Backup-Files im Verzeichnis behalten. Um nur 2 Files zu behalten wird das Attribut gesetz:

attr Rep.fhemtest.Dump.ServerSide dumpFilesKeep 2


c) Aktionen vor und nach dem Backup ausführen

Vor und nach der Dump-Ausführung kann ein FHEM-Kommando bzw. Perl-Befehle ausgeführt werden. Perl-Befehle müssen in { } eingeschlossen werden.

Für das Beispiel soll vor dem Backup die Verbindung des DbLog-Devices LogDB zur Datenbank getrennt werden. Dadurch werden keine neuen Daten in die Datenbank geschrieben. Wenn das DbLog-Device LogDB im asynchronen Modus wie empfohlen betrieben wird, tritt auch kein Datenverlust ein, da die zu loggenden Daten im Cache verbleiben bis die Verbindung zur DB wiederhergestellt wird.

Um die Verbindung zu trennen wird ein

set LogDB reopen <Zeit in Sekunden> 

verwendet. Die Zeitspanne wird sehr großzügig gewählt und soll sicherstellen, dass innerhalb dieser Zeit das Backup abgeschlossen ist. Nach dem Backup wird sofort

set LogDB reopen 

ausgeführt, was die Verbindung des DbLog-Devices LogDB zur Datenbank unmittelbar wiederherstellt.

attr Rep.fhemtest.Dump.ServerSide executeBeforeProc set LogDB reopen 3600
attr Rep.fhemtest.Dump.ServerSide executeAfterProc set LogDB reopen


d) vor dem Backup Datenbank verkleinern (optimizeTables)

Diese optionale Funktion wird durch das Attribut "optimizeTablesBeforeDump" eingeschaltet. Dadurch diese Funktion wird Platz innerhalb der Datenbank freigegeben der durch Löschvorgänge entstanden ist und dadurch die Größe des Datenbankfiles verringert.

attr Rep.fhemtest.Dump.ServerSide optimizeTablesBeforeDump 1

Durch die Tabellenoptimierung verlängert sich die Dumpzeit.


e) das angelegte Dump-File nach dem Backup zum FTP-Server übertragen

DbRep bietet die Möglichkeit, das erstellte Dump-File per FTP oder verschlüsselt per FTPS zum FTP-Server zu übertragen. Dazu gibt es einen Satz von Attributen um dem Device alle notwendigen Angaben für den FTP-Transfer zur Verfügung zu stellen. Es gibt dafür folgende Attribute:


  • ftpUse : FTP Transfer nach dem Dump wird eingeschaltet (bzw. ftpUseSSL mit SSL Verschlüsselung)
  • ftpUser : User zur Anmeldung am FTP-Server, default: anonymous
  • ftpPwd : Passwort des FTP-Users, default nicht gesetzt
  • ftpDir : Verzeichnis auf dem FTP-Server in welches das File übertragen werden soll (default: FTP-root)
  • ftpPort : FTP-Port, default: 21
  • ftpServer : Name oder IP-Adresse des FTP-Servers
  • ftpTimeout : Timeout für die FTP-Verbindung in Sekunden (default: 30)
  • ftpPassive : setzen wenn passives FTP verwendet werden soll
  • ftpDebug : Debugging des FTP Verkehrs zur Fehlersuche


Gemäß dieser Beschreibung und dem Ziel dieses Beispiels werden die für FTP-Transfer relevanten Attribute wie folgt gesetzt:


attr Rep.fhemtest.Dump.ServerSide ftpDir /ftp                # Subdirectory ftp unter FTP-root als Zielverzeichnis
attr Rep.fhemtest.Dump.ServerSide ftpPwd ftpftp1
attr Rep.fhemtest.Dump.ServerSide ftpServer sds1.<fqdn>
attr Rep.fhemtest.Dump.ServerSide ftpUse 1
attr Rep.fhemtest.Dump.ServerSide ftpUser ftpuser


Der FTP-Server muss natürlich vorab eingerichtet und funktionsfähig sein. Sollten beim FTP-Transfer Fehler auftreten, kann das Attribut ftpDebug = 1 gesetzt werden um entsprechende Ausgaben zur Analyse des Problems im Log zu erhalten.

Fertig definiertes Device Rep.fhemtest.Dump.ServerSide



f) Hilfsattribute

Für die Funktion nicht relevant aber informativ ist das Attribut "showproctime = 1". Ist das Attribut gesetzt, wird das Reading "background_processing_time" angelegt. Es enthält nach der Ausführung die verbrauchte Prozesszeit.

Im Attribut "userExitFn" kann eine Perl-Routine hinterlegt werden, um zum Beispiel nach dem Backup eine Mail oder Telegram-Message mit dem Erfolgsstatus zu versenden.


Das fertig konfigurierte Device hier als RAW-Definition zur einfachen Nachnutzung. Die Angaben sind natürlich anzupassen:

define Rep.fhemtest.Dump.ServerSide DbRep LogDB
attr Rep.fhemtest.Dump.ServerSide devStateIcon connected:10px-kreis-gelb .*disconnect:10px-kreis-rot .*done:10px-kreis-gruen .*Dump.*is.*running.*:remotecontrol/black_btn_PLAYgreen Database.*backup.*finished.*:remotecontrol/black_btn_GREEN tn_GREEN error.*:remotecontrol/black_btn_RED
attr Rep.fhemtest.Dump.ServerSide dumpCompress 1
attr Rep.fhemtest.Dump.ServerSide dumpDirLocal /sds1/backup/dumps_FHEM
attr Rep.fhemtest.Dump.ServerSide dumpDirRemote /volume1/ApplicationBackup/dumps_FHEM
attr Rep.fhemtest.Dump.ServerSide dumpFilesKeep 1
attr Rep.fhemtest.Dump.ServerSide event-on-update-reading state
attr Rep.fhemtest.Dump.ServerSide executeAfterProc set LogDB reopen
attr Rep.fhemtest.Dump.ServerSide executeBeforeProc set LogDB reopen 3600
attr Rep.fhemtest.Dump.ServerSide fastStart 1
attr Rep.fhemtest.Dump.ServerSide ftpDebug 0
attr Rep.fhemtest.Dump.ServerSide ftpDir /ftp
attr Rep.fhemtest.Dump.ServerSide ftpDumpFilesKeep 3
attr Rep.fhemtest.Dump.ServerSide ftpPwd ftpftp1
attr Rep.fhemtest.Dump.ServerSide ftpServer sds1.<fqdn>
attr Rep.fhemtest.Dump.ServerSide ftpUse 0
attr Rep.fhemtest.Dump.ServerSide ftpUser ftpuser
attr Rep.fhemtest.Dump.ServerSide optimizeTablesBeforeDump 1
attr Rep.fhemtest.Dump.ServerSide showproctime 1
attr Rep.fhemtest.Dump.ServerSide userExitFn doafterdump



3. Backup durchführen

Voraussetzung ist, dass der verwendete Datenbankuser das globale Privileg FILE besitzt. Falls der User dieses Recht nicht hat, kann man es mit dem definierten DbRep-Device wie folgt erledigen.


(optional) dem Datenbankuser das FILE Privileg zuweisen

Da die Rechtezuweisung nur mit einem administrativen DB-User (i.A. root) funktioniert, wird dieser User im DbRep-Device angelegt und aktiviert:

 set  Rep.fhemtest.Dump.ServerSide adminCredentials <Admin-User> <Passwort>
 attr Rep.fhemtest.Dump.ServerSide useAdminCredentials 1

Nun kann dem verwendeten Datenbankuser das FILE Privileg zugeordnet werden:

set  Rep.fhemtest.Dump.ServerSide sqlCmd GRANT FILE ON *.* TO '<DB-User>'@'%';

Dabei ist <DB-User> durch den in der DbLog-Konfiguration angegebenen User zu ersetzen da die gesamte Datenbankarbeit mit diesem User erfolgt.

Nach der Ausführung schaltet man die Nutzung des administrativen Users wieder ab mit:

 attr Rep.fhemtest.Dump.ServerSide useAdminCredentials 0

Die Rechte können nun überprüft werden mit

set  Rep.fhemtest.Dump.ServerSide sqlCmd show grants;

und werden im Ergebnis dargestellt (z.B. für den DB-User fhemtest):

SqlResultRow_1  GRANTS FOR FHEMTEST@%
SqlResultRow_2  GRANT PROCESS, FILE, INDEX ON *.* TO 'fhemtest'@'%' IDENTIFIED BY PASSWORD '*...............'
SqlResultRow_3  GRANT SELECT, INSERT, UPDATE, DELETE, ALTER, EXECUTE, SHOW VIEW ON `fhemtest`.* TO 'fhemtest'@'%'
SqlResultRow_4  GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, INDEX ON `fhemtest`.`fhemtest` TO 'fhemtest'@'%'



Backup starten
Backup läuft ...

Mit dem wie beschrieben eingerichteten DbRep-Device kann das Backup gestartet werden mit:

set Rep.fhemtest.Dump.ServerSide dumpMySQL serverSide


Zu Beginn des Vorgangs wird sofort die Verbindung des DbLog-Devices zur Datenbank getrennt. Ein laufendes serverSide Backup kann mit FHEM-Mitteln nicht abgebrochen werden !

Das laufende Backup wird im state angezeigt mit "serverSide Dump is running - be patient and see Logfile !".


Im Logfile mit (mindestens) verbose 3 werden die relevanten Informationen zur Verfügung gestellt:

2020.05.14 22:59:18.610 3: DbRep Rep.fhemtest.Dump.ServerSide - ########################################
2020.05.14 22:59:18.611 3: DbRep Rep.fhemtest.Dump.ServerSide - ###   New database serverSide dump   ###
2020.05.14 22:59:18.611 3: DbRep Rep.fhemtest.Dump.ServerSide - ########################################
2020.05.14 22:59:18.612 3: DbRep Rep.fhemtest.Dump.ServerSide - execute command before dump: 'set LogDB reopen 3600' 
2020.05.14 22:59:18.613 2: DbLog LogDB: Connection closed until 23:59:18 (3600 seconds).
2020.05.14 22:59:18.660 3: DbRep Rep.fhemtest.Dump.ServerSide - Searching for tables inside database fhemtest....
2020.05.14 22:59:18.664 3: DbRep Rep.fhemtest.Dump.ServerSide - Size of database fhemtest before optimize (MB): 8298.98
2020.05.14 22:59:18.665 3: DbRep Rep.fhemtest.Dump.ServerSide - Optimizing tables
2020.05.14 22:59:18.665 3: DbRep Rep.fhemtest.Dump.ServerSide - Optimizing table `current` (INNODB). It will take a while.
2020.05.14 22:59:19.560 3: DbRep Rep.fhemtest.Dump.ServerSide - Table 1 `current` optimized successfully.
2020.05.14 22:59:19.560 3: DbRep Rep.fhemtest.Dump.ServerSide - Optimizing table `history` (INNODB). It will take a while.
2020.05.14 23:30:09.183 3: DbRep Rep.fhemtest.Dump.ServerSide - Table 2 `history` optimized successfully.
2020.05.14 23:30:09.186 3: DbRep Rep.fhemtest.Dump.ServerSide - 2 tables have been optimized.
2020.05.14 23:30:09.253 3: DbRep Rep.fhemtest.Dump.ServerSide - Size of database fhemtest after optimize (MB): 8298.98
2020.05.14 23:30:09.254 3: DbRep Rep.fhemtest.Dump.ServerSide - Starting dump of database 'fhemtest', table 'history'
2020.05.14 23:39:47.566 3: DbRep Rep.fhemtest.Dump.ServerSide - compress file /sds1/backup/dumps_FHEM/fhemtest_history_2020_05_14_23_30.csv
2020.05.14 23:42:33.138 3: DbRep Rep.fhemtest.Dump.ServerSide - file compressed to output file: /sds1/backup/dumps_FHEM/fhemtest_history_2020_05_14_23_30.csv.gzip
2020.05.14 23:42:34.784 3: DbRep Rep.fhemtest.Dump.ServerSide - input file deleted: /sds1/backup/dumps_FHEM/fhemtest_history_2020_05_14_23_30.csv
2020.05.14 23:42:34.785 3: DbRep Rep.fhemtest.Dump.ServerSide - Number of exported datasets: 49032159
2020.05.14 23:42:34.787 3: DbRep Rep.fhemtest.Dump.ServerSide - Size of backupfile: 419.73 MB
2020.05.14 23:42:37.082 3: DbRep Rep.fhemtest.Dump.ServerSide - FTP: transferring /sds1/backup/dumps_FHEM/fhemtest_history_2020_05_14_23_30.csv.gzip
2020.05.14 23:42:47.511 3: DbRep Rep.fhemtest.Dump.ServerSide - FTP: fhemtest_history_2020_05_14_23_30.csv.gzip transferred successfully to sds1.myds.me into dir /ftp
2020.05.14 23:42:47.558 3: DbRep Rep.fhemtest.Dump.ServerSide - Deleting old dumpfile 'fhemtest_history_2020_05_14_22_12.csv.gzip' 
2020.05.14 23:42:47.857 3: DbRep Rep.fhemtest.Dump.ServerSide - Finished backup of database fhemtest - total time used (hh:mm:ss): 00:43:29
2020.05.14 23:42:47.948 2: DbRep Rep.fhemtest.Dump.ServerSide - command message after dump: "Reopen executed." 
2020.05.14 23:42:47.973 3: DbRep Rep.fhemtest.Dump.ServerSide - Database dump finished successfully. 
Backup ist erfolgreich abgeschlossen


In vorliegenden Beispiel wird nach einem erfolgreichen Backup im state "Warning - dump finished, but command after dump message appeared" angezeigt.

Diese Warnung rührt daher, dass das Reopen-Kommando eine Rückkehrinfo mitteilt, die als Issue gewertet und im Reading "afterdump_message" ausgegeben wird. In diesem Fall ist es nur eine Information.


In den erstellten Readings wird zusammengetragen welches File mit welcher Größe erstellt wurde, welche alten Files gelöscht wurden, Informationen zum FTP-Transfer und welche Zeit der Gesamtprozess benötgt hat.


Das so vorbereitete Backup kann nun z.B. täglich oder auch mit einer wesentlich kürzeren Wiederholungsperiode (alle x Stunden) über ein at-Device eingeplant werden:

define At.MySQL.Dump at *23:52:00 set Rep.fhemtest.Dump.ServerSide dumpMySQL serverSide



4. Restore

Voraussetzung für das Restore ist, dass der verwendete Datenbankuser das globale Privileg FILE besitzt. Falls der User dieses Recht nicht hat, kann man es mit dem definierten DbRep-Device wie folgt erledigen.


(optional) dem Datenbankuser das FILE Privileg zuweisen

Da die Rechtezuweisung nur mit einem administrativen DB-User (i.A. root) funktioniert, wird dieser User im DbRep-Device angelegt und aktiviert:

 set  Rep.fhemtest.Dump.ServerSide adminCredentials <Admin-User> <Passwort>
 attr Rep.fhemtest.Dump.ServerSide useAdminCredentials 1

Nun kann dem verwendeten Datenbankuser das FILE Privileg zugeordnet werden:

set  Rep.fhemtest.Dump.ServerSide sqlCmd GRANT FILE ON *.* TO '<DB-User>'@'%';

Dabei ist <DB-User> durch den in der DbLog-Konfiguration angegebenen User zu ersetzen da die gesamte Datenbankarbeit mit diesem User erfolgt.

Nach der Ausführung schaltet man die Nutzung des administrativen Users wieder ab mit:

 attr Rep.fhemtest.Dump.ServerSide useAdminCredentials 0

Alternativ kann die Rechtezuweisung auf der Konsole des MySQL-Servers ausgeführt werden:

sudo mysql -u root -p
GRANT File ON *.* TO '<DB-User>'@'%';
exit

Die Rechte können nun überprüft werden mit

set  Rep.fhemtest.Dump.ServerSide sqlCmd show grants;


Nun kann der Restore ausgeführt werden.


Ein mit der Option serverSide erstellter Dump enthält ausschließlich die Daten der history-Tabelle. Der Restore kann nur in eine bestehende Datenabank und entsprechend angelegte history Tabelle erfolgen. Ist nach einem Datenbankfehler die Tabelle/Datenbank zerstört, must sie zunächst mit den vorbereiteten Befehlen in den SVN-Skripten bereitgestellt werden.

Ist die history-Tabelle noch intakt und soll nur der Inhalt ersetzt werden, muss die Tabelle vorab geleert werden da sonst unter Umständen doppelte Datensätze enstehen wenn kein primary Key verwendet ist. Das kann einfach mit dem Befehl

set <Name> sqlCmd truncate table history

erreicht werden. Damit werden alle Daten der history-Tabelle hochperformant gelöscht. Die Tabelle selbst bleibt erhalten.


Sollte der truncate Befehl wegen nicht ausreichender Rechte des DB-Users mit Fehler enden, kann ein administrativer Datenbankuser (i.A. 'root') mit dem Kommando und Attribut

set  <Name> adminCredentials <Name> <Passwort>
attr <Name> useAdminCredentials 1

hinterlegt und aktiviert werden.


Ein Restore kann mit dem angelegten DbRep-Device im laufenden Betrieb geschehen. Auch hier ist wieder wichtig, dass das DbLog-Device im asynchronen Modus betrieben wird und die Reopen-Zeit (siehe Einstellung Attribut "executeBeforeProc") sehr großzügig eingestellt ist.

Zum Restore dient der Befehl

set <Name> restoreMySQL <File>
Auswahlmenü Files für Restore


Im FHEMWEB öffnet sich dazu eine DropDown-Liste die alle vorhandenen und zur Quelldatenbank passenden Dumpfiles auflistet.

Das herzustellende File kann so bequem ausgewählt werden. Die Dumpfiles müssen sich in dem Verzeichnis befinden, welches durch das Attribut dumpDirLocal festgelegt wurde (default (./log).

Restore läuft


Der restore wird demzufolge gestartet mit:

set Rep.fhemtest.Dump.ServerSide restoreMySQL <File>


Der Restore der history-Tabelle kann nun online im laufenden FHEM-Betrieb erfolgen. Wieder wird zu Beginn des Vorgangs das verbundene DbLog-Device von der Datenbank abgekoppelt und nach dem Restore wieder automatisch verbunden. Eine Datenbankoptimierung bzw. FTP-Transfer wird bei diesem Vorgang natürlich nicht ausgeführt.


Fortschrittsanzeige in einem zweiten DbRep Device

In einem zweiten (z.B. kopierten) DbRep Device kann mit Hilfe des Befehls


get Rep.fhemtest.Dump.ServerSide procinfo


der Fortschritt des Restores überwacht werden. In der Spalte PROGRESS wird der Fortschritt in % angegeben.


Das Log zeigt den Ablauf des Restores:

2020.05.15 13:07:24.369 3: DbRep Rep.fhemtest.Dump.ServerSide - #######################################
2020.05.15 13:07:24.370 3: DbRep Rep.fhemtest.Dump.ServerSide - ###   New database Restore/Recovery ###
2020.05.15 13:07:24.371 3: DbRep Rep.fhemtest.Dump.ServerSide - #######################################
2020.05.15 13:07:24.371 3: DbRep Rep.fhemtest.Dump.ServerSide - execute command before restore: 'set LogDB reopen 3600' 
2020.05.15 13:07:24.429 3: DbRep Rep.fhemtest.Dump.ServerSide - uncompress file /sds1/backup/dumps_FHEM/fhemtest_history_2020_05_15_11_47.csv.gzip
2020.05.15 13:09:31.152 3: DbRep Rep.fhemtest.Dump.ServerSide - file uncompressed to output file: /sds1/backup/dumps_FHEM/fhemtest_history_2020_05_15_11_47.csv
2020.05.15 13:09:31.237 3: DbRep Rep.fhemtest.Dump.ServerSide - Size of uncompressed file: 5511.52 MB
2020.05.15 13:09:31.242 3: DbRep Rep.fhemtest.Dump.ServerSide - Starting restore of database 'fhemtest', table 'history'. 
2020.05.15 14:42:28.566 2: DbLog LogDB: Connection closed until 16:42:28 (7200 seconds).
2020.05.15 14:46:22.391 3: DbRep Rep.fhemtest.Dump.ServerSide - Restore of /volume1/ApplicationBackup/dumps_FHEM/fhemtest_history_2020_05_15_11_47.csv into 'fhemtest', 'history' finished - total time used (hh:mm:ss): 01:38:57
2020.05.15 14:46:22.457 2: DbRep Rep.fhemtest.Dump.ServerSide - command message after restore: "Reopen executed." 
2020.05.15 14:46:22.481 3: DbRep Rep.fhemtest.Dump.ServerSide - Database restore finished successfully.
Restore erfolgreich abgeschlossen


Der Restore wurde erfolgreich abgeschlossen und die Verbindung des DbLog-Device LogDB zur Datenbank wiederhergestellt. Das Logging wird nahtlos fortgeführt.

Es ist natürlich zu beachten, dass die Daten zwischen dem Erstellungszeitpunkt des eingespielten Backups und der aktuellen Zeit verloren sind ! Eventuell in der Tabelle history noch vorhandene Daten werden nicht überschrieben.



Speichern von Berechnungswerten in der Datenbank und Erstellen eines Plots (ab Version 7.5.1)

Es sollen aus in der Datenbank vorhandenen minütlichen Leistungswerten eines Wechselrichters die maximal-, minimal- und durchschnittlichen Leistungswerte pro Tag berechnet werden und diese Ergebnisse wieder in die Datenbank geschrieben werden um daraus einen Plot zu erstellen. Diese Berechnung soll immer aktuell für den laufenden Monat erfolgen.

Die Ausgangswerte liegen in der Datenbank als minütliche kW-Einträge vor (Auszug DbRep fetchrows):

2018-01-18_15-55-49__MySTP_5000__total_pac 0.200
2018-01-18_15-56-50__MySTP_5000__total_pac 0.218
2018-01-18_16-00-14__MySTP_5000__total_pac 0.209
2018-01-18_16-00-39__MySTP_5000__total_pac 0.198
2018-01-18_16-01-39__MySTP_5000__total_pac 0.142
2018-01-18_16-02-39__MySTP_5000__total_pac 0.130
2018-01-18_16-03-39__MySTP_5000__total_pac 0.117
2018-01-18_16-05-39__MySTP_5000__total_pac 0.087
2018-01-18_16-06-39__MySTP_5000__total_pac 0.071
2018-01-18_16-07-39__MySTP_5000__total_pac 0.076
2018-01-18_16-08-39__MySTP_5000__total_pac 0.065
2018-01-18_16-09-39__MySTP_5000__total_pac 0.069
2018-01-18_16-10-39__MySTP_5000__total_pac 0.060
2018-01-18_16-12-39__MySTP_5000__total_pac 0.065
2018-01-18_16-13-39__MySTP_5000__total_pac 0.068
2018-01-18_16-14-39__MySTP_5000__total_pac 0.054
2018-01-18_16-15-39__MySTP_5000__total_pac 0.041
2018-01-18_16-16-39__MySTP_5000__total_pac 0.027
2018-01-18_16-17-39__MySTP_5000__total_pac 0.026
2018-01-18_16-18-39__MySTP_5000__total_pac 0.021
2018-01-18_16-19-39__MySTP_5000__total_pac 0.018
2018-01-18_16-20-39__MySTP_5000__total_pac 0.012

Für die Berechnung der Ergenisse stehen in DbRep die Kommandos maxValue, minValue und averageValue zur Verfügung.



1. Anlegen des DbRep-Devices

Das anzulegende Device wird für alle notwendigen Berechnungen verwendet. Die Definition des DbRep-Devices erfolgt unter Angabe des zu verbindenden DbLog-Devices (nicht der Datenbank selbst):

define Rep.total_pac DbRep LogDB

Ist das Device definiert, verbindet es sich zu der Datenbank und wechselt in den state "connected", sofern die Verbindung erfolgreich verlief. Prinzipiell funktioniert der hier Ablauf mit einem im synchronous Mode betriebenen DbLog-Device, aber es ist dringend angeraten das DbLog-Device (LogDB) in den asynchronen Modus umzuschalten. Dadurch werden Blockierungen von FHEM verhindert.



2. Einstellungen des DbRep-Devices (Attribute)

Device definiert

Für die gewünschte Funktion müssen in diesem neu definierten Device einige relevante Attribute gesetzt werden.

Da die Berechnungen immer für den aktuellen Monat erstellt werden sollen, wird das Attribut "timestamp_begin" so definiert, dass immer der Beginn des aktuellen Monats dynamisch ermittelt wird. Das ist gegeben durch den Eintrag:

attr Rep.total_pac timestamp_begin current_month_begin

Es soll ein Ergebniswert pro Tag erzeugt werden. Damit dies erreicht werden kann, wird das Attribut "aggregation" verwendet und auf den Wert "day" eingestellt. Würde zum Beispiel ein stündlicher Ergebniswert gewünscht sein, hätte "aggregation = hour" den benötigten Effekt.

attr Rep.total_pac aggregation day

Die Attribute "device" und "reading" legen das auszuwertende Device und Reading fest. Dabei ist zu beachten, dass im Gegensatz zur allgemein gültigen Möglichkeit devspec bzw. SQL-Wildcards zu verwenden bei dieser speziellen Detailfunktion dies nicht der Fall ist. Das heißt. es muß immer ein eindeutiges Device und ein eindeutiges Reading angegeben werden. Der Grund liegt in der Notwendigkeit, bei der Speicherung der Ergebnisdaten der Datenbank ebenfalls ein eindeutiges Device und Reading zu übergeben. In dem Beispiel wird das Device "MySTP_5000" und das Reading "total_pac" ausgewertet.

attr Rep.total_pac device MySTP_5000
attr Rep.total_pac reading total_pac

Hilfreiche Attribute sind noch "event-on-update-reading" zur Begrenzung der Events, "showproctime" zur Bestimmung der Prozesszeiten und "allowDeletion" um die Möglichkeit zu haben, irrtümlich gespeicherte Berechnungsergebnisse wieder über das DbRep-Device zu löschen.

attr Rep.total_pac event-on-update-reading state
attr Rep.total_pac showproctime 1
attr Rep.total_pac allowDeletion 1

Das so vorbereitete Device ist für die benötigten Funktionen ausreichend eingestellt. Zur einfachen Nachnutzung nochfolgend die Raw-Definition des DbRep-Devices:

define Rep.total_pac DbRep LogDB
attr Rep.total_pac aggregation day
attr Rep.total_pac allowDeletion 1
attr Rep.total_pac device MySTP_5000
attr Rep.total_pac event-on-update-reading state
attr Rep.total_pac reading total_pac
attr Rep.total_pac room DbLog
attr Rep.total_pac showproctime 1
attr Rep.total_pac timestamp_begin current_month_begin
attr Rep.total_pac verbose 3



3. Berechnung ausführen und Speicherung der Ergebnisse

Average Berechnung

Die benötigten Funktionen sind:

averageValue - Berechnung des täglichen Durchschnittswert der Wechselrichterleistung (kW)
maxValue - Berechnung des täglichen Maximalwertes der Wechselrichterleistung (kW)
minValue - Berechnung des täglichen Minimalwertes der Wechselrichterleistung (kW)
MinVal Berechnung

Für jede dieser Kommandos stehen im DbRep die Optionen "display" und "writeToDB" zur Verfügung. Die Option "display" stellt die Ergenisse lediglich im Browser dar und kann dazu die Ergebnisse vor der Speicherung zu bewerten. So ergeben die Kommandos:

set Rep.total_pac minValue display 
set Rep.total_pac maxValue display 
set Rep.total_pac avarageValue display 

die täglichen Min-, Max- und Durchschnittswerte der WR-Leistung.

MaxVal Berechnung

Entsprechen die Ergebnisse den Erwartungen bzw. dem Ziel, können die folgenden Kommandos verwendet werden um die Berechnung durchzuführen und dabei gleichzeitig in die Datenbank zu schreiben.

set Rep.total_pac minValue writeToDB
set Rep.total_pac maxValue writeToDB
set Rep.total_pac avarageValue writeToDB

Die Daten werden in der history-Tabelle gespeichert und, sofern das DbLog-Device das Attribut "DbLogType = Current..." gestzt hat, auch in der current-Tabelle gespeichert. Im letzteren Fall kann das Ergebnisreading bei der Erstellung des Plots sofort aus der DropDown-Liste ausgewählt werden.

Bei der Datenspeicherung wird der neue Readingnamen aus einem Präfix und dem originalen Readingnamen gebildet. Der Präfix setzt sich aus der Bildungsfunktion und der gewählten Aggregation zusammen, d.h. im Fall des Beispiels aus "min","max","avg" und "day". Der Timestamp der neuen Readings in der Datenbank wird von der eingestellten Aggregationsperiode abgeleitet, sofern kein eindeutiger Zeitpunkt des Ergebnisses bestimmt werden kann. So kann der Zeitpunkt des Maximalwertes eines Tages wertes genau bestimmt werden und der resultierende Datensatz wird mit diesem Timestamp gespeichert.

Die resultierende Readingnamen werden somit wie folgt in der Datenbank gespeichert:

max_day_total_pac
min_day_total_pac
avg_day_total_pac

Die Berechnungen können regelmäßig über ein at eingeplant werden. Die Funktionen vermeiden dabei die Speicherung von doppelten Datensätzen. Wurde bei einem Berechnungslauf der Datensatz bereits gespeichert, wird dieser Datensatz beim nächsten Durchlauf nicht noch einmal gespeichert.

4. Erstellung des Plots

Drop-Down Liste SVG

Wird durch das DbLog-Device die current-Tabelle genutzt, das Attribut "DbLogType = Current..." ist gesetzt, können die Ergebnisreadings max_day_total_pac, min_day_total_pac und avg_day_total_pac im SVG-Editor ausgewählt werden (siehe commandref/SVG bei SVG).

Durch die Wahl des Plot-Typ "bars" und dem SVG-Attribut "fixedrange = month" entsteht der gewünschte Tageswert-Plot über den aktuellen Monat.

Ergebnis SVG

Hier wiederum die Raw-Definition des SVG-Device:

defmod SVG_LogDB_16 SVG LogDB:SVG_LogDB_16:HISTORY
attr SVG_LogDB_16 fixedrange month
attr SVG_LogDB_16 room Energie,SVG_MySQL
attr SVG_LogDB_16 title "Max Leistung: $data{max1} kW, Min Leistung: $data{min3} kW,Max. Durchschnittsleistung: $data{max2} kW "

sowie des gplot-Files:

# Created by FHEM/98_SVG.pm, 2018-01-18 21:45:27
set terminal png transparent size <SIZE> crop
set output '<OUT>.png'
set xdata time
set timefmt "%Y-%m-%d_%H:%M:%S"
set xlabel " "
set title '<TL>'
set ytics 
set y2tics 
set grid
set ylabel "kW"
set y2label "kW"
set yrange [0:6]
set y2range [0:6]

#LogDB MySTP_5000:max_day_total_pac
#LogDB MySTP_5000:avg_day_total_pac
#LogDB MySTP_5000:min_day_total_pac

plot "<IN>" using 1:2 axes x1y2 title 'Max-Leistung / Tag' ls l0fill lw 1 with bars,\
     "<IN>" using 1:2 axes x1y2 title 'Average / Tag' ls l2fill lw 1 with bars,\
     "<IN>" using 1:2 axes x1y2 title 'Min-Leistung / Tag' ls l1fill lw 1 with bars



Öffnungszeiten von Fenster/Türen aus der Datenbank ermitteln

Ziel ist es, die Öffnungszeit eines Fensters oder einer Tür zu ermitteln sobald es wieder geschlossen wird und ein Reading mit der Zeit in dem entsprechenden Device (Fensterkontakt) zu setzen.

Für das Beispiel wird angenommen dass:

  • der Status der Sensoren als "open" und "close" in der Datenbank gespeichert ist
  • das verwendetet DbRep-Device "Rep.WindowOpenTime" heißt
  • das verwendete DbLog-Device "LogDBShort" heißt (wird mit dem DbRep-Device bei der Definition assoziiert)


Zunächst wird zur Auswertung von closed-Events ein entsprechendes Notify definiert (Raw-Format):

defmod N.WindowClosed notify (.*fenster.*|.*terrasse.tuer.*):closed \
  { \
    if($NAME =~ /(.*fenster.*|.*terrasse.tuer.*)/) {\
      fhem("set LogDBShort commitCache;; defmod calcse at +00:00:10 {SwitchEvent(\"$NAME\")};;");; \
    }\
  }
attr N.WindowClosed alias Starte Ermittlung der Fenster Öffnungszeiten
attr N.WindowClosed comment Wird verwendet um die letzte Öffnungszeit der Fenster aus der Datenbank zu ermitteln und im Reading "LastOpenTime" abzulegen.
attr N.WindowClosed room Datenbank->Produktiv

Die zusätzlich if-Abfrage im Notify behob ein Problem bei den Fensterkontakten die mit einem Thermostat gepeert sind, da in diesem Fall der Event mehrfach auftrat. Der Teil "set LogDBShort commitCache" speichert den Cache der Datenbank vor der Berechnung sofern sie im asynchronen Modus betrieben wird (kann entfallen wenn im synchronen Mode betrieben). Um der DB Zeit zur Abspeicherung zu geben, wird die Berechnung über ein AT mit einer entsprechenden Verzögerung (hier 10 Sekunden) angestartet. Die Zeit im AT bitte entsprechend der eigenen Systembedingungen anpassen.

Im Notify wird die Funktion SwitchEvent($NAME) aufgerufen. Diese Funktion wird in der 99_myUtils.pm eingefügt:

##########################################################################################################
#  This function is used to start an SQL query for the given device "$name" and check
#  how much time the device was in state defined by $usedVal1 today.
#  The query is done by a DbRep device.
# 
#  In this example the name of the DbRep device is "Rep.WindowOpenTime"
##########################################################################################################
sub SwitchEvent($) {
  my ($name) = @_;
 
  Log3 $name , 5, "$name - SwitchEvent called";
 
  my $usedReading = "\"state\"";           # name of the reading which holds the state which has to be checked
  my $usedVal1 = "\"open\"";               # reading value which starts the time measuerement
  my $usedVal2 = "\"closed\"";             # reading value which stops the time measurement
  my $device = "\"$name\"";                # name of the device which state has to be checked
  my $dbRepDevice = "Rep.WindowOpenTime";  # name of the DbRep device
 
  # The query will check for the current day the cumulated time difference between $usedVal1 and $usedVal2
  # the example here is a homemetic threeStateSensor (window contact) and the result of the query is
  # the cumulated time which the window was open (in seconds) and the name of the device whic is
  # used by the function SwitchQueryResult()
  # Example:  AZ_Fensterkontakt|433.2
  my $sql = "SET \@topen = NULL,\@closed = NULL, \@popen = NULL;;".
            " SELECT  DEVICE ,SUM(TIMEDIFF(tclosed, topen)) AS duration FROM (".
            " SELECT TIMESTAMP, VALUE, DEVICE,".
            " \@topen   := \@popen AS topen,".
            " \@closed  := IF(VALUE = $usedVal2,  TIMESTAMP, NULL) AS tclosed,".
            " \@popen   := IF(VALUE = $usedVal1, TIMESTAMP, NULL) AS prev_open".
            " FROM history WHERE".
            " DATE(TIMESTAMP) = CURDATE() AND".
            " DEVICE = $device AND".
            " READING = $usedReading AND ".
            " (VALUE = $usedVal1 OR VALUE = $usedVal2)".
            " ORDER BY  TIMESTAMP".
            " ) AS tmp";
 
  Log3 $name , 5, "$name - SwitchEvent start query: $sql";
 
  fhem("set $dbRepDevice sqlCmd $sql"); # start the query now. Result will be processed in the function SwitchQueryResult() below
 
return;
}

Die Funktion SwitchEvent() bekommt den Namen des Gerätes übergeben welches Event ausgelöst hat. Damit wird eine SQL-Abfrage für dieses Gerät gestartet und ermittelt, wieviel Zeit (in Sekunden) für das übergebene Gerät zwischen dem Zustand "open" und "closed" verbracht wurde.

Das verwendete DbRep-Device wird wie folgt definiert:

define Rep.WindowOpenTime DbRep LogDBShort
attr Rep.WindowOpenTime aggregation no
attr Rep.WindowOpenTime comment Öffnungszeit der Fenster ermitteln
attr Rep.WindowOpenTime initialized:control_3dot_hor_s connected:10px-kreis-gelb .*disconnect:10px-kreis-rot .*done:10px-kreis-gruen
attr Rep.WindowOpenTime reading state
attr Rep.WindowOpenTime showproctime 1
attr Rep.WindowOpenTime sqlResultFormat sline
attr Rep.WindowOpenTime event-on-update-reading state
attr Rep.WindowOpenTime userExitFn SwitchQueryResult

Ganz wichtig ist die Zeile attr Rep.powerOnTime userExitFn SwitchQueryResult. Hier ist die Funktion SwitchQueryResult() angegeben, die nach erfolgreicher Bearbeitung der SQL-Query aufgerufen wird. Diese Funktion wird ebenfalls in der 99_myUtils.pm eingefügt:

##########################################################################################################
# This function will be called from the DbRep device if the attribute
# attr Name_of_your_DbRep_device userExitFn SwitchQueryResult is set.
# This function create a new reading "LastOpenTime" at the device for which the query is done and
# set the cumulated time difference in seconds to the reading.
##########################################################################################################
sub SwitchQueryResult($$$) {
   my ($name,$reading,$value) = @_;  # $name is the name of the DbRep device which is calling this function
   my $hash = $defs{$name};
 
   # DbRep calls this function several times with different informations.
   # if $reading is SqlResult, $value will hold the result of the query
   if ($reading eq "SqlResult") {
       my ($dev,$val) = split('\|',$value);                   # split the result in two parts
       $dev = $dev?$dev:"";
       $val = $val?$val:"";
       if (!$val) {
           $val = 0;
       }
       $val = DbRep_sec2hms($val);                            # calculate seconds to hms
       Log3 $name , 5, "DbRep $name - SwitchQueryResult splitted result: $dev $val";
 
       fhem("setreading $dev LastOpenTime $val") if($dev);    # set the reading "LastOpenTime" with the time if device-Log was found
   }
 
return;
}

Die Funktion SwitchQueryResult() rechnet das Ergebnis der query aus dem DbRep Device von Sekunden in das Format hh:mm:ss um und trägt die berechnete Zeit als Reading "LastOpenTime" im jeweiligen Fensterkontakt ein.

So kann man z.B. im Device mit dem Namen "eg.bad.fenster" das Reading "LastOpenTime 00:03:58" finden. Die vorliegende Berechnung liefert immer die Zeit des letzten Öffnungszyklus des aktuellen Tages. Möchte man die Zeiten des gesamten Tages akkumulieren, kann nach der Beschreibung "Summe aller Einschaltzeiten eines Gerätes" verfahren werden.

Grünlandtemperatursumme ermitteln und in SVG-Grafik darstellen

Nachfolgendes Beispiel zeigt, wie die Grünlandtemperatursumme ermittelt werden kann und die Ermittlungsergebnisse in der Datenbank gespeichert werden, um sie in einer SVG-Grafik darzustellen. Das beschriebene Verfahren kann natürlich auch für andere Anwendungsfälle angewendet werden.

Was ist die Grünlandtemperatursumme?

Hier die Definition aus Wikipedia:

Die Grünlandtemperatursumme (GTS) ist eine Spezialform der Wachstumsgradtage, die in der Agrarmeteorologie verwendet wird. Sie wird herangezogen, um in Mitteleuropa den Termin für das Einsetzen der Feldarbeit nach dem Winter zu bestimmen.
Eine Wärmesumme ist allgemein eine gewisse Lufttemperatur eines Tages über die Tage einer Periode summiert. Dabei verwendet man besonders die kumulierte korrigierte GTS, die nach Monat gewichtet wird.
Wird im Frühjahr die Summe von 200 überschritten, ist der nachhaltige Vegetationsbeginn erreicht. Hintergrund ist die Stickstoffaufnahme und -verarbeitung des Bodens, welcher von dieser Temperatursumme abhängig ist. In mittleren Breiten wird das meist im Laufe des März, an der Wende von Vorfrühling zu Mittfrühling erreicht.

Zur Bestimmung der GTS benötigt man zunächst die Aufzeichnung der örtlichen Temperaturwerte in einer DbLog-Datenbank. Im Beispiel wird dazu das Reading temperature des Device MyWetter (Device vom Typ Weather) genutzt. Die Einrichtung des Weather- und DbLog-Devices wird an dieser Stelle nicht beschrieben und als bekannt vorausgesetzt.

Es wird angenommen, dass:

  • das definierte DbLog-Device im asynchronen Mode betrieben wird
  • das Reading temperature des Device MyWetter in der DbLog-Datenbank gespeichert wird

Zur Ermittlung des GTS wird ein DbRep-Device mit gesetztem Attribut avgDailyMeanGWSwithGTS verwendet. Dieses Attribut legt die Berechnung der täglichen Durchschnittstemperatur nach den Regularien des Deutschen Wetterdienstes fest und aktiviert auf dieser Grundlage weiterhin die Berechnung der Grünlandtemperatursumme.

GTS Ermittlung

Die RAW-Definition des verwendeten DbRep-Devices:

define Rep.GTS DbRep LogDB
attr Rep.GTS averageCalcForm avgDailyMeanGWSwithGTS
attr Rep.GTS device MyWetter
attr Rep.GTS fastStart 1
attr Rep.GTS reading temperature
attr Rep.GTS room DbLog
attr Rep.GTS showproctime 1
attr Rep.GTS timestamp_begin current_year_begin
attr Rep.GTS timestamp_end previous_day_end
attr Rep.GTS userExitFn saveGTS

LogDB ist der Name des angeschlossenen DbLog-Devices und ist natürlich entsprechend anzupassen. Unbedingt wichtig ist es, den Start der Auswertung auf den Beginn des Jahres zu setzen. Das Attribut timestamp_begin = current_year_begin erfüllt diese Bedingung. Das Attribut userExitFn wird noch erläutert.

Die Berechnung wird gestartet mit dem Befehl:

set Rep.GTS averageValue display

Im Ergebnis entstehen Readings die auszugsweise nachfolgend aufgelistet sind:

     2021-02-26 22:32:08   2021-02-11__MyWetter__temperature__AVGDMGWS__2021-02-11 -5.1
     2021-02-26 22:32:08   2021-02-11__MyWetter__temperature__GrasslandTemperatureSum 40.1
     2021-02-26 22:32:08   2021-02-12__MyWetter__temperature__AVGDMGWS__2021-02-12 -6.8
     2021-02-26 22:32:08   2021-02-12__MyWetter__temperature__GrasslandTemperatureSum 40.1
     2021-02-26 22:32:08   2021-02-13__MyWetter__temperature__AVGDMGWS__2021-02-13 insufficient values - execute get Rep.GTS versionNotes 2 for further information
     2021-02-26 22:32:08   2021-02-14__MyWetter__temperature__AVGDMGWS__2021-02-14 -8.2
     2021-02-26 22:32:08   2021-02-14__MyWetter__temperature__GrasslandTemperatureSum 40.1
     2021-02-26 22:32:08   2021-02-15__MyWetter__temperature__AVGDMGWS__2021-02-15 -3.2
     2021-02-26 22:32:08   2021-02-15__MyWetter__temperature__GrasslandTemperatureSum 40.1

Die Meldung "insufficient values - execute get Rep.GTS versionNotes 2 for further information" ist ein Hinweis darauf, dass die für die Ermittlung der Durchschnittstagestemperaturen nach den Regularien des Deutschen Wettederdienstes geforderten Werte nicht (komplett) in der DB vorhanden sind.

Um die Grünlandtemperatursumme im SVG-Diagramm anzuzeigen, muss dieser Wert für jeden Tag in der DB gespeichert werden. Alle benötigten Informationen sind in den DbRep-Readings, z.B.:

2021-02-12__MyWetter__temperature__GrasslandTemperatureSum 40.1

vorhanden. Der Präfix enthält das Datum, für das die jeweilige Grünlandtemperatursumme gilt, d.h., es muß nur noch ein neuer Wert zu diesem korrespondierenden Datum in die DB geschrieben werden. Das wird durch eine kleine Routine in 99_myUtils.pm erreicht, die im Attribut userExitFn des DbRep-Devices hinterlegt wird.

Routine in 99_myUtils.pm einfügen:

##############################################################
#              speichern GTS in Datenbank
##############################################################
sub saveGTS {
  my $name    = shift;
  my $reading = shift;
  my $value   = shift;
  my $device  = "MyWetter";  # Weather-Device -> anpassen !
  my $logdev  = "LogDB";     # DbLOg Device   -> anpassen !
  
  # 2021-02-14__MyWetter__temperature__GrasslandTemperatureSum 40.1
  if($reading =~ /GrasslandTemperatureSum/x) {
      my ($date)    = (split "__", $reading)[0];
	  my ($y,$m,$d) = split "-", $date;
	  my $ts        = "$y-$m-$d 12:00:00";
	  
      CommandSet(undef, "$logdev addCacheLine $ts|$device|calculated|calculated|GrasslandTemperatureSum|$value|");  
  }

return;
}

In der Routine sind die Variablen $device und $logdev mit den realen Devices Weather und DbLog zu ersetzen.

Funktionsweise
Ist der Auswertungslauf beendet, werden alle Readings, die "GrasslandTemperatureSum" enthalten, in ihre Bestandteile aufgesplittet, der Timestamp zum Speichern in der Datenbank formatiert und der neu in der DB zu erstellende Datensatz in den Cache des DbLog-Devices eingefügt. Der nächste Sync-Lauf in DbLog fügt die im Cache erstellten Datensätze in die Datenbank ein.

Definition des SVG-Devices

GTS SVG Darstellung

In der Datenbank befinden sich nun Datensätze mit dem Device MyWetter und dem Reading GrasslandTemperatureSum die im SVG Diagramm ausgewertet werden können.

RAW-Definition des SVG-Devices:

defmod SVG_GTS SVG LogDB:SVG_GTS:HISTORY
attr SVG_GTS fixedrange month
attr SVG_GTS label "aktuelle Grünlandtemperatur $data{max1}"
attr SVG_GTS room SVG

Die dazugehörige GPLOT-Datei (SVG_GTS.gplot):

# Created by FHEM/98_SVG.pm, 2021-02-27 08:08:30
set terminal png transparent size <SIZE> crop
set output '<OUT>.png'
set xdata time
set timefmt "%Y-%m-%d_%H:%M:%S"
set xlabel " "
set title '<L1>'
set ytics 
set y2tics 
set grid
set ylabel "Score"
set y2label "Score"

#LogDB MyWetter:GrasslandTemperatureSum::

plot "<IN>" using 1:2 axes x1y2 title 'GTS' ls l1fill lw 1 with ibars