Um bestimmte Prozesse wie die Anmeldung, die bei allen Gerätesteuerungen gleich sind, zu vereinfachen, gibt es eine Art kleine JavaScript und Python 'Bibliothek'. So wird es etwas einfacher, eine Gerätesteuerung für ein neues Gerät zu schreiben. In diesem Artikel werden die Bibliotheken beschrieben. Die JS und Python Versionen sind ähnlich aufgebaut, werden aber etwas unterschiedlich verwendet.

Init

Besonders für die Gerätesteuerungen ist, dass sie sich mit einem Gerätenamen anmelden. Pro Gerätename kann sich nur ein laufendes Skript anmelden, um sich bei den Daten in den Kanälen nicht in die Quere zu kommen. Das ist ein Problem beim Kühlschrank, da dort mehrere Skripte (mehrere Raspis) laufen, die aber alle den Kühlschrank Kanal verwenden. Aus diesem Grund kann der Gerätename z.B. als kuehlschrank.kameras.innen angegeben werden. Dann wird der Kanal kuehlschrank verwedet, aber Daten werden immer geschrieben in:

{ // Kanal `kuehlschrank`
    "kameras": {
        "innen": { /* Daten hier */ }
    }
}

Python

# Threads sind nicht zwingend notwendig, aber praktisch falls es sich
# um ein Skript mit Main-Loop handelt.
from threading import Event, Thread
exit_event = Event()

# Import: Die Datei liegt in `geraete/interface.py` und kann so von Gerätesteuerungen
# in einem Unterordner davon importiert werden
import sys
sys.path.append("..")
import interface as smart_home

@smart_home.init("fenster")
def init(_config):
    # ... Initialisierung alle Variablen anhand der Config, die
    # hier mitgegeben wird. Diese Funktion muss als einziges immer
    # vorhanden sein.


@smart_home.on_quit
def on_quit():
    # ... Code, der ausgeführt werden soll oder muss, wenn das Skript
    # beendet werden soll (z.B. beim Reset). Zum Beispiel kann hier
    # ein Event ausgelöst werden, das einen parallelen Thread stoppt:
    exit_event.set()


@smart_home.on("shellies")
def shellies(data):
    # ... Diese Funktion wird aufgerufen, wenn Daten im Kanal `shellies`
    # geupdated wurden. Der Kanal wird automatisch abboniert.


@smart_home.on("fenster:mein-event")
def handler(arg1, arg2):
    # ... Handler für das Event `fenster:mein-event`. Der Kanal wird
    # automatisch abboniert sofern notwendig.

    # Sollte der Handler zum Beispiel neue Daten schreiben wollen,
    # geht das mit der Funktion `update_data()`. Das JS Object im
    # Argument wird mit dem im Ziel gemerged. Z.B. vorher/nachher:
    #   "fenster": {"offen": False, "fehler": False}
    smart_home.update_data({"offen": True})
    #   "fenster": {"offen": True, "fehler": False}

    # Bilder oder andere Dateien können über `update_cache()` in den
    # Server cache geladen werden:
    update_cache(fname, url, callback)


@smart_home.on_activate
def on_activate():
    # ... Wird aufgeführt, wenn das Gerät aktiviert werden soll.
    # Es gibt gleichermaßen wie Funktion `on_deactivate`.
    # Bei deaktivierten Geräten wird automatisch das Empfangen von
    # Events deaktiviert, das heißt darum muss sich das Skript nicht
    # selbst kümmern.
    # Falls ein Gerät nicht wie sonst standardmäßig aktiviert ist,
    # wird `on_deactivate` direkt nach `init` aufgerufen.

    # Falls die Statusänderung nicht sofort ist, kann der Status
    # auf pending gesetzt werden (und dann später wieder normal):
    update_device_status(True)  # pending=True


def main():
    pass


t = Thread(target=main, daemon=True)
t.start()

# Baut die Socket.IO Verbindung auf
smart_home.connect()

# Beendet das Programm, wenn der Thread sich beendet
t.join()
smart_home.disconnect()

JS

// Import (relativ aus einem Unterordner in `geraete`)
const smartHome = require("../interface.js");

var config;


smartHome.init("sensfloor", (_config) => {
    // ... Initialisierung alle Variablen anhand der Config, die
    // hier mitgegeben wird. Diese Funktion muss als einziges immer
    // vorhanden sein.
    config = _config;
    
    // `updateData()` schreibt Daten in den Kanal des Geräts.
    // Damit kann zum Beispiel hier direkt beim Init schon was
    // geschrieben werden.
    smartHome.updateData({"verbunden": null});
});

// Anm.: In der JS Version braucht es kein onQuit, das Hilfsskript kann
// sich selbst um das Beenden kümmern.

smartHome.on("sensfloor", (data) => {
    // ... Wird aufgerufen, wenn Daten im Kanal `sensfloor` geupdated werden.
    // Der Kanal wird, falls nötig, automatisch abboniert.
})
smartHome.on("sensfloor:mein-event", (arg1, arg2) => {
    // ... Genauso wie Datenupdates können auch Handler für eigene Events
    // registriert werden. Der Kanal wird automatisch abboniert.

    // Falls Bilder oder andere Dateien in den Cache geladeb werden sollen,
    // geht das mit  `updateCache()`:
    updateCache(fname, url, callback);
});

// Auf diese Art kann benutzerdefinierter Code ausgeführt werden, wenn
// das Gerät aktiviert wird. Bei deaktivieren Geräten werden automatisch
// keine Events mehr empfangen. Geräte sind standarmäßig aktiviert, das
// kann über die Config aber geändert werden. In dem Fall wird direkt
// nach dem Aufruf von `init()` hier `onDeactivate()` aufgerufen.
smartHome.onActivate(() => {
    // Falls eine Statusänderung nicht sofort ist, kann der Status
    // auf pending (und später wieder auf normal) gesetzt werden:
    updateDeviceStatus(true);  // pending=true
});
smartHome.onDeactivate(sensFloorOff);  // Fürs Deaktivieren gleichermaßen

// Anders als in der Python Version, wird in der JS Version mit dem Aufruf von `init`
// direkt die Verbindung aufgebaut.