BPMN im Smart Home: Camunda und openHAB

Keine Kommentare

Geschäftsprozessmodellierung und einhergehende Sprachen wie BPMN und DMN sind Begriffe, denen man normalerweise im beruflichen Umfeld begegnet und die im privaten Raum keine Rolle spielen. Natürlich kann man die Prozesse eines Haushalts (aka kleines, privates Familienunternehmen) auch mit Hilfe der BPMN abbilden, doch jeder würde mir beipflichten, dass dies ein wenig “drüber” wäre. Warum sollte man also mit Kanonen auf Spatzen schießen und BPMN im Smart Home verwenden? Für mich ist die Frage ganz klar zu beantworten: Weil es geht!

Es ergibt nach wie vor keinen wirklichen Sinn, eine Prozessautomatisierung bzw. -steuerung im privaten Rahmen zu implementieren, doch rein aus Interesse habe ich die Verknüpfung meines Smart Home-openHAB-Systems mit der Camunda BPE (Camunda Business Process Engine) ausgetestet und muss sagen: Kann man machen! Anstatt Regeln über die in openHAB integrierte Rule-Engine zu definieren, modelliere ich sie nun als BPMN-Diagramm. Hierfür war es notwendig, einen MQTT-Broker aufzusetzen, über den die Integration von Camunda und openHAB realisiert werden konnte. Den entsprechenden Source-Code kann man im GitHub-Repository finden.

OpenHAB und MQTT

Für mein Proof-of-Concept habe ich auf Eclipse Mosquitto als Message Broker zurückgegriffen, welches ich über Docker laufen lasse.

version: '3.5'
services:
  mosquitto:
    image: eclipse-mosquitto
    ports:
      - "1883:1883"
      - "9001:9001"
    volumes:
      - ./sh-mosquitto/mosquitto.conf:/mosquitto/config/mosquitto.conf

Läuft der Broker, müssen wir openHAB nur noch “beibringen”, sich mit diesem zu verbinden. Gewiefte openHAB-Nutzer werden erahnen, dass hierfür ein Binding installiert werden muss. Das MQTT-Binding (https://www.openhab.org/addons/bindings/mqtt/) kann über die Paper-UI oder die addons.cfg-Datei wie gewohnt installiert werden. Anschließend muss der Mosquitto-Broker noch mit openHAB verheiratet werden, indem man ihn als Thing konfiguriert. Da ich mein openHAB-System über Dateien konfiguriere, folgt hier ein Auszug aus meiner Konfigurationsdatei:

Bridge mqtt:broker:broker [ host="mosquitto" ] {
    Thing topic wohnzimmer "MQTT Wohnzimmer" {
    Channels:
        Type switch : Lampe_Decke [ stateTopic="Haus/Wohnzimmer/Lampe/Decke", commandTopic="Haus/Wohnzimmer/Lampe/Decke", postCommand=true, on="ON", off="OFF"]
        Type switch : Lampe_Wuerfel [ stateTopic="Haus/Wohnzimmer/Lampe/Wuerfel", commandTopic="Haus/Wohnzimmer/Lampe/Wuerfel", postCommand=true, on="ON", off="OFF" ]
        Type switch : Lampe_Kamera [ stateTopic="Haus/Wohnzimmer/Lampe/Kamera", commandTopic="Haus/Wohnzimmer/Lampe/Kamera", postCommand=true, on="ON", off="OFF" ]
    }
}

Hier passiert bereits die eine Hälfte der Magie die nötig ist, um Camunda und openHAB zusammen zu bringen: Innerhalb der Konfiguration der MQTT-Bridge werden für drei Lampen die Channels vom Typ “switch” (sic! Entgegen der Definition in den Items wird „switch“ hier klein geschrieben) definiert, welche einem State- bzw. Command-Topic zugeordnet werden. Möchte man später über das entsprechende Topic die Geräte schalten, muss unbedingt der Parameter postCommand auf true gesetzt werden, da sonst das Licht aus bleibt bzw. der Schalter nicht schaltet. Die Zuordnung von on auf ON bzw. off auf OFF ist für die spätere Nutzung in Camunda relevant, da hier sonst eine 1 statt ON bzw. eine 0 statt OFF ankommt. Je nach Gusto kann dies natürlich auch weggelassen und mit den jeweiligen Zahlenwerten weitergearbeitet werden.

Nach der Konfiguration des MQTT-Things, also der Verbindung zum Broker, müssen wir nun noch die Channel aus der Broker-Konfiguration mit den korrespondierenden Items verbinden.

Switch Wohnzimmer_Wuerfellampe
    "Würfellampe"
    { channel="hue:0100:1:wuerfellampe:brightness", channel="mqtt:topic:broker:wohnzimmer:Lampe_Wuerfel" }
 
Switch Wohnzimmer_Decke
    "Deckenlampe"
    { channel="homematic:HmIP-FSM:ccu:00089A49967AA7:2#STATE", channel="mqtt:topic:broker:wohnzimmer:Lampe_Decke" }
 
Switch Wohnzimmer_Kameralampe
    "Kameralampe"
    { channel="hue:0100:1:kameralampe:brightness", channel="mqtt:topic:broker:wohnzimmer:Lampe_Kamera" }

Die gezeigte Konfiguration enthält neben der MQTT-Verknüpfung auch die Verknüpfung zu den eigentlichen Schaltzentralen der einzelnen Lampen. So werden bspw. die Würfel- und Kameralampe über Philips Hue gesteuert und die Deckenlampe über Homematic.
Wird über die openHAB-Oberfläche die Würfellampe eingeschaltet, wird der Befehl “Einschalten” bzw. “ON” initiiert und von openHAB über das Hue-Binding an die Hue-Bridge weitergeleitet, welche letztlich das Licht schaltet. Analog geschieht dies bei Homematic-Geräten. Darüber hinaus wird der Befehl auch auf den MQTT-Broker gesendet und kann von allen Systemen, welche sich mit dem Broker verbunden haben, dort abgerufen werden.

Über MQTT sieht der Vorgang wie folgt aus: Sendet man eine Nachricht über Mosquitto an das Topic für die Deckenlampe mit dem Payload “ON”, wird openHAB diese Nachricht lesen, verarbeiten und die Deckenlampe schalten, indem der Befehl „ON“ über das Homematic-Binding an die Homematic CCU weitergeleitet wird, welche wiederum über eine 433Mhz Frequenz den eigentlichen Schalter ansteuert.

mosquitto_pub -t "Haus/Wohnzimmer/Lampe/Decke" -m "ON"

BPMN im Smart Home: Mind the gap between openHAB and Camunda!

OpenHAB und Mosquitto sind nun konfiguriert und funktionieren wie gewollt: Es werden Nachrichten bei Zustandsänderungen gesendet und Nachrichten können eine Zustandsänderung hervorrufen. Der nächste Schritt ist, eine Verbindung zwischen Mosquitto und Camunda herzustellen.

Um ein greifbares Szenario zu haben, sei folgender Use Case gegeben: Es sollen, wenn die Deckenlampe im Wohnzimmer eingeschaltet wird, sowohl die Würfel- als auch Kameralampe ebenfalls eingeschaltet werden. Wird die Deckenlampe ausgeschaltet, soll nichts weiter passieren.

Schauen wir einmal im Detail auf die von der BPMN angebotenen Konzepte, welche für die Umsetzung eingesetzt werden können: Die BPMN bietet sowohl Messages als auch Signals an. Rein technisch betrachtet existiert zwischen den beiden Konzepten kein Unterschied, semantisch jedoch ist ein Signal einem Broadcast gleichzusetzen und eine Message ein gezielter Versand einer Nachricht an einen externen Akteur. Geht man nun davon aus, dass das Schalten einer Lampe (hier der Deckenlampe) ein von openHAB gesendeter Broadcast über den Message-Broker ist, können wir als Startereignis ein “Signal Start Event” verwenden. Soll aus der BPMN heraus eine Lampe geschaltet werden, ist dies als gerichteter Aufruf zu verstehen und lässt sich daher als Message modellieren. Heißt also: eingehende Zustandsänderungen sind Signals, ausgehende Zustandsänderungen sind Messages.

Mit Hilfe der BPMN lässt sich der Use Case daher wie folgt abbilden:

BPMN-Prozess zur Steuerung der Würfel- und Kameralampe, wenn die Deckenlampe angeschaltet wird.

Wird die Deckenlampe im Wohnzimmer geschaltet, haben wir festgelegt, dass der entsprechende Zustand auf das MQTT-Topic Haus/Wohnzimmer/Lampe/Decke gelegt wird. Das “Signal Start Event” muss also auf dieses Topic horchen und wird daher im Feld “Signal Name” eingetragen.

Einstellungen des Signal Start Events zum Anstoßen des BPMN-Prozesses.

Nächster Schritt ist die Rückantwort von der BPMN an openHAB. Da es sich hier um das gezielte Senden von Nachrichten handelt, greifen wir auf “Intermediate Throw Events” zurück und geben als Message-Namen das Topic an, welches die entsprechende Lampe adressiert. Im vorliegenden Fall ist dies Haus/Wohnzimmer/Lampe/Kamera sowie Haus/Wohnzimmer/Lampe/Wuerfel. Was nun noch fehlt ist der Befehl, den wir an das korrespondierende Item übermitteln wollen. Dies wird am Message-Namen noch angefügt, sodass dieser letztlich wie folgt aussieht: Haus/Wohnzimmer/Lampe/Kamera:command:ON bzw. Haus/Wohnzimmer/Lampe/Wuerfel:command:ON.

Einstellungen des Intermediate Throw Events zum anschalten der Kameralampe.

Camunda an MQTT: Kommunikation mit der Umwelt über ThrowEvents

Ist die Ablauflogik in Camunda definiert, fehlt nun noch die ausführende Schicht, welche den Workflow und die Verbindung zum MQTT-Broker realisiert. Dies nehmen wir in Kotlin vor und verknüpfen unsere Implementierung durch das Camunda-Konzept “Delegate Expression” mit den modellierten “Intermediate Throw Events”.

Da wir Camunda als Spring-Boot-Applikation implementieren, können wir im “Delegate Expression”-Feld den Namen unserer auszuführenden Bean eintragen ${mqttThrowEvent} und die Camunda BPE löst dies zur Laufzeit auf. Der Code sieht wie folgt aus:

package de.stekoe.camunda.mqtt.bpm
 
import de.stekoe.camunda.mqtt.logger
import de.stekoe.camunda.mqtt.service.MqttService
import org.camunda.bpm.engine.delegate.DelegateExecution
import org.camunda.bpm.engine.delegate.JavaDelegate
import org.camunda.bpm.model.bpmn.instance.Message
import org.camunda.bpm.model.bpmn.instance.MessageEventDefinition
import org.camunda.bpm.model.bpmn.instance.ThrowEvent
import org.springframework.stereotype.Component
 
@Component
class MqttThrowEvent(private val mqttService: MqttService) : JavaDelegate {
    override fun execute(execution: DelegateExecution) {
        val topic = getTopic(execution)
        if (topic == null) {
            logger().error("Not throwing any event in {}. Could not determine topic.", execution.currentActivityName)
            return
        }
 
        getMqttMessagePayload(execution)?.let { message ->
            logger().info("Throwing event topic {} with value {}", topic, message)
            mqttService.sendMessage(topic, message)
        }
    }
 
    private fun getTopic(execution: DelegateExecution): String? {
        return getBpmnMessageName(execution)?.split(":command:".toRegex())?.get(0)
    }
 
    private fun getMqttMessagePayload(execution: DelegateExecution): String? {
        return getBpmnMessageName(execution)?.split(":command:".toRegex())?.last()
    }
 
    private fun getBpmnMessage(execution: DelegateExecution): Message {
        val event = execution.bpmnModelElementInstance as ThrowEvent
        val definition = event.eventDefinitions.iterator().next() as MessageEventDefinition
        return definition.message
    }
 
    private fun getBpmnMessageName(execution: DelegateExecution): String? {
        return try {
            val bpmnMessage = getBpmnMessage(execution)
            bpmnMessage.name
        } catch (e: Exception) {
            logger().info("Cannot get the Message Name.", e)
            null
        }
    }
}

Die obige Implementierung liest den Message-Namen des Events aus und zerlegt diesen anhand des Keywords „command“ in den Topic und den Befehl, welcher an das Topic gesendet wird. So wird bspw. aus Haus/Wohnzimmer/Lampe/Kamera:command:ON das Topic Haus/Wohnzimmer/Lampe/Kamera und der Befehl ON extrahiert. Die Anbindung an den MQTT-Broker ist in den MqttService gekapselt.

MQTT an Camunda: Vom Message-Broker zum BPMN-Signal

Wie eingangs geschrieben, setzen wir für das Starten unseres BPMN-Prozesses auf das „Signal-Start-Event“. Hierfür stellt Camunda die Methode signalEventReceived im RuntimeService bereit, welche einen Signalnamen und optional weitere Parameter entgegennimmt, welche der Prozessinstanz übergeben werden können. In unserem PoC verwenden wir das MQTT-Topic als Signalnamen und geben zudem den Payload der MQTT-Message und Namen des MQTT-Topics als Variablen an den Prozess weiter.

override fun messageArrived(topic: String, message: MqttMessage) {
    logger().info("Message arrived at '{}' with payload {}", topic, message)
 
    val values: MutableMap<String, Any> = HashMap()
    values[String.format(PAYLOAD_NAME_PATTERN, topic.replace("/", "_"))] = String(message.payload)
    values[String.format(TOPIC_NAME_PATTERN, topic.replace("/", "_"))] = topic
 
    try {
        // Instruct Camunda BPE to trigger signal events
        runtimeService.signalEventReceived(topic, values)
    } catch (e: ProcessEngineException) {
        logger().warn("Signal processing failed due to: ${e.message}")
    }
 
    logger().info("Throwing BPMN signal '{}' width value {}", topic, values)
}

Erhalten wir nun eine MQTT-Nachricht, wird in der Camunda-Engine ein BPMN-Signal mit demselben Namen angestoßen. Jedes Signal-Event, das auf dieses Signal lauscht, stößt dann die entsprechende Ablauflogik an, was in unserem Falle zum Start einer Prozessinstanz zur Steuerung der Lampen führt.

Fazit

Durch das Bindeglied Mosquitto ist es möglich, openHAB und Camunda zusammenzubringen und eine Steuerung über BPMN-Diagramme zu ermöglichen. Wie anfangs bereits erwähnt ist dies eher ein konstruiertes Problem, zeigt aber, wie flexibel verschiedene Systeme integriert werden können. Gerade im Hinblick auf die Heimautomatisierung ist die Verwendung von Camunda eine zwar technisch aufwändigere, aber auch autarke Alternative zu IFTTT. Da ich meine Heimautomatisierung komplett unabhängig von externen Cloud-Anbietern halte, um bei Ausfällen des Internets immer noch alle Aktionen/Szenen und Geräte steuern zu können, werde ich diesem Ansatz jedoch eine Probezeit gewähren. Für visuell orientierte Menschen ist dieser Ansatz auch zu empfehlen, da man die Ablauflogik als Grafik präsentiert bekommt und keine abstrakten Code-Schnipsel eintippen muss.

Stephan Köninger

Stephan arbeitet als Software-Entwickler mit dem Schwerpunkt Web-Frontend-Entwicklung für die codecentric. Themen wie Java, Spring und Hibernate sind ihm nicht fremd, was ihn zu einem Full-Stack-Entwickler macht. Zudem ist das Thema Automatisierung sowie Testing ebenfalls wichtiger Bestandteil seines täglichen Arbeitens.

Über 1.000 Abonnenten sind up to date!

Die neuesten Tipps, Tricks, Tools und Technologien. Jede Woche direkt in deine Inbox.

Kostenfrei anmelden und immer auf dem neuesten Stand bleiben!
(Keine Sorge, du kannst dich jederzeit abmelden.)

Hiermit willige ich in die Erhebung und Verarbeitung der vorstehenden Daten für das Empfangen des monatlichen Newsletters der codecentric AG per E-Mail ein. Ihre Einwilligung können Sie per E-Mail an datenschutz@codecentric.de, in der Informations-E-Mail selbst per Link oder an die im Impressum genannten Kontaktdaten jederzeit widerrufen. Von der Datenschutzerklärung der codecentric AG habe ich Kenntnis genommen und bestätige dies mit Absendung des Formulars.

Kommentieren

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.