- Erste Schritte zur eigenen Programmierung
Vorwort
Auf Wunsch vieler Leser zeige ich Dir in dieser Serie, wie Du FHEM nicht nur konfigurieren, sondern eigene kleine Funktionen für FHEM programmieren kannst. So kannst Du nicht nur einfach Änderungen vornehmen, sondern auch komplexe Szenarien und Funktionen realisieren.
Dieser Programmierkurs richtet sich an Anfänger und Fortgeschrittene, wobei ich mit den Grundlagen anfangen möchte.
Bitte teile mir in den Kommentaren mit, ob die Erklärungen verständlich und ob die Beispiele für Dich nützlich sind. Du kannst mir auch gerne Deinen Wunsch für Funktionen und Themen schreiben oder wenn Du selbst eine nützliche Funktion geschrieben hast.
Bevor wir mit praktischen Beispielen für FHEM starten, möchte ich kurz einige Grundlagen und Prinzipien der Programmierung vorstellen. Dabei möchte ich besonders auf die Grundsätze guter Programmierung eingehen – keine Angst, so kompliziert ist es nicht und irgendwann verinnerlicht man diese. Vor allem verhindert man damit den bekannten „Spaghetti-Code“.
Grundlagen: Das Prinzip „Clean Code“
Grundsätzlich versteht man unter Clean Code die Verständlichkeit und Lesbarkeit der Programmierung. Geprägt wurde der Begriff durch den Software-Entwickler Robert C. Martin. Für den Anfang erachte ich die folgenden Prinzipien für sinnvoll:
- Eindeutige und sinnvolle Benennung von Klassen, Objekten, Funktionen und Variablen.
- Kleine Funktionen, die immer nur eine Aufgabe erfüllen.
- Code lässt sich lesen „wie eine Zeitung“ – Kommentare sind überflüssig.
- Trennen der unterschiedlichen Abstraktionsebenen.
- Klare und verständliche Ablaufsteuerung (keine Rückgabe von NULL-Werten, Fehler abfangen).
Die letzten beiden Prinzipien klingen etwas komplizierter, sind aber sehr wichtig. Besonders für Anfänger ist es schwer die einzelnen Abstraktionsebenen abzugrenzen. Hier zwei Code-Beispiele zur Verdeutlichung:
Einheitliche Abstraktionsebenen
function SpeichereKonfiguration() {
LöscheBestehendeDatei();
ErstelleXML();
SpeichereXML();
}
Die Funktion hat eine sprechende Bezeichnung und ist eine Funktion auf höherer Abstraktionsebene, da sie nur grob den Ablauf beschreibt, was generell zu tun ist. Wie z. B. das Löschen der Datei genau aussieht, beschreibt sie nicht, dies geschieht eine Ebene tiefer.
Eine schlechte Programmierung erkennt man daran, dass die unterschiedlichen Abstraktionsebenen in einer Funktion vermischt werden. Das Ergebnis: Spaghetti-Code. Und wenn man diesen Code erweitern will, findet man die Stelle nicht oder muss die Änderung an mehreren Stellen vornehmen.
Vermischung von Abstraktionsebenen
function SpeichereKonfiguration() {
if (File.Exists(Filename)) {
File.Delete(Filename);
}
ErstelleXML();
SpeichereXML();
}
In diesem Beispiel wird bereits genau beschrieben, wie eine Datei gelöscht wird und die Aufrufe mit einer anderen Ebene vermischt.
Wenn Du mehr über Clean Code erfahren willst, empfehle ich Dir dieses Buch:
Wann bietet sich die Auslagerung in eigene Programmdateien an?
Sinnvoll ist es aus meiner Sicht immer eigene Programmdateien anzulegen in denen alles „gesammelt“ wird, das in den notify- und at-Anweisungen in FHEM aufgerufen wird. Egal ob es für eine kleine Funktion ist oder nicht. So kann man jederzeit die Programmierung erweitern.
In meinem Fall habe ich die einzelnen Funktionen in den Programmdateien je nach Verwendung in separate Dateien (Module) aufgeteilt. So gibt es zum Beispiel die Datei 99_dkUtils.pm, die grundsätzliche Funktionen beherbergt, 99_dkHandleMedia.pm, die für alles rund um das Thema Media verantwortlich ist oder 99_dkHandleTimer.pm, in der die Funktionen der einzelnen Timer beschrieben sind. Teilweise werden auch Funktionen der anderen Module aufgerufen.
Wie Du Deine Module anlegst, ist natürlich Dir überlassen. Für mich hat sich diese Vorgehensweise bewährt, da ich so schnell Fehler beheben oder Erweiterungen vornehmen kann.
Eigene Helfer-Programme in FHEM anlegen
In dem Wiki von FHEM findet man ein sehr gutes Tutorial zu diesem Thema. Grundsätzlich ist eine eigene Datei wie folgt aufgebaut:
99_myUtils.pm
package main;
use strict;
use warnings;
use POSIX;
sub myUtils_Initialize($$) {
my ($hash) = @_;
}
/* Eigene Funktionen */
1;
Wichtig hier sind zunächst folgende Dinge:
- Der Dateiname beginnt immer mit „99_“. So werden die eigenen Module am Ende des Systemstarts geladen.
- Ein eigenes Modul beginnt immer mit der Initialisierungsfunktion.
- Der Funktionsname muss immer dem Titel der Datei entsprechen. In diesem Fall myUtils_Initialize($$). Meine 99_dkUtils.pm hat den Funktionsnamen dkUtils_Initialize($$).
- Anschließend folgen die eigenen Funktionen.
- Die Datei endet immer mit „1;“, danach darf kein Zeichen mehr folgen.
Wer möchte kann noch eine Dokumentation für seinen Code erzeugen. Wie das geht findest Du in obigen FHEM-Tutorial im FHEM-Wiki. Dies empfiehlt sich aus meiner Sicht nur, wenn man ein Modul geschrieben hat, da in die offizielle FHEM-Distribution aufgenommen werden soll.
Und wie sieht das Ganze nun in FHEM aus?
fhem.cfg
define 10pm_timer at *22:00 {dkHandle10pm()}
attr 10pm_timer group Timer
attr 10pm_timer room Timer
Der Timer ruft täglich um 22:00 Uhr die Funktion dkHandle10pm() auf.
99_dkHandleTimer.pm
sub dkHandle10pm() {
fhem("set Deko off", 1);
}
In der Funktion dkHandle10pm() wird das Device – in meinem Fall ist es ein Stockwerk – ausgeschaltet.
Für mich als „Anfänger“ was Programmierung angeht, sind die Inhalte gut verständlich. Die von Dir angebotenen Config-Files fand ich schon sehr sprechend, mit den hier vorgestellten Prinzipien dahinter wird es noch transparenter – besten Dank 😉
Hallo, ich hätte eine kleine Frage:
sub dkHandle10pm() {
fhem(„set Deko off“, 1);
}
Wofür steht die 1 im FHEM-Befehl?
Hi Max,
es verhindert das Schreiben des Wertes in die Logdatei.
Hier ein Auszug aus der FHEM Doku:
Ich hoffe, dass ich Dir damit helfen konnte. Ich unterbinde damit quasi, dass die Logdatei zugemüllt wird.
Zum Debuggen habe ich meine eigene Funktion geschrieben und wenn ich etwas ins Log geschrieben haben will, dann möchte ich dies explizit bestimmen.
Viele Grüße
Dennis