1. 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:

  1. Der Dateiname beginnt immer mit „99_“. So werden die eigenen Module am Ende des Systemstarts geladen.
  2. Ein eigenes Modul beginnt immer mit der Initialisierungsfunktion.
  3. Der Funktionsname muss immer dem Titel der Datei entsprechen. In diesem Fall myUtils_Initialize($$). Meine 99_dkUtils.pm hat den Funktionsnamen dkUtils_Initialize($$).
  4. Anschließend folgen die eigenen Funktionen.
  5. 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.