top of page

Schritt für Schritt: Xesar-Event-Listener mit Kotlin und Spring Boot

  • Autorenbild: Andreas Grill
    Andreas Grill
  • 13. Juni 2024
  • 5 Min. Lesezeit

Aktualisiert: 12. Feb.

In diesem Blog Artikel zeigen wir, wie einfach es ist, das smarte elektronische Schließsystem Xesar von EVVA mit einem MQTT-Listener zu verbinden und auf Events im System zu reagieren.


Wir verwenden dafür die Open-Source Bibliothek Xesar-Connect, deren Entwicklung von der open200 unterstützt wird. Xesar-Connect ermöglicht es, einfach und komfortabel die asynchrone Schnittstelle von Xesar zu nutzen.


Xesar Event Bus

Das elektronische Schließsystem Xesar von EVVA bietet eine MQTT-Schnittstelle, welche es ermöglicht, auf verschiedene Events im System zu reagieren. Dazu zählen unter anderem Hinweise auf leer werdende Batterien, offene Wartungsaufträge, und vieles mehr.


In diesem Blogartikel wollen wir einen einfachen MQTT-Listener implementieren, der das Event “Battery Empty” behandelt und den Namen des betroffenen Einbauortes bzw. EVVA-Komponente ausgibt.


Tech Stack

  • JDK 21

  • Kotlin 1.9

  • Spring Boot 3.2

  • Xesar-Connect 1.0


Wir nutzen Kotlin und Spring Boot für die Implementierung des MQTT-Listeners. Xesar-Connect ermöglicht einen einfachen und effizienten Zugriff auf die Xesar asynchrone MQTT-Schnittstelle mit Hilfe von Kotlin Coroutinen.


Xesar-Connect ist auch mit älteren JDK Versionen kompatibel, wie zum Beispiel JDK 11. Allerdings empfehlen wir die Verwendung von JDK 21 oder neuer für die neuesten Features und Verbesserungen.


Account in Xesar anlegen

Damit wir Zugriff auf die Xesar-MQTT-Schnittstelle erhalten, benötigen wir einen Benutzer in der Xesar-Anlage. Wir erstellen daher einen eigenen Benutzer den wir nur für die Integration nutzen mit einem generierten Passwort. Wir empfehlen in Produktivumgebungen die Verwendung von starken Passwörtern und das nutzen von Benutzergruppen mit minimalen Rechten.

User account form in German, fields filled: username, description, strong password. Active status checked. Green strength bar.
Anlegen eines Benutzers in Xesar-Connect

Nun können wir die “Konfiguration” des Benutzers als ZIP-Datei herunterladen. In der Konfiguration sind die notwendigen Daten für die Verbindung und Authentifizierung enthalten.

A software interface in German shows user details for batteryemptylistener. A yellow button reads "Konfiguration herunterladen."
Herunterladen der Xesar-Konfiguration

Projekt-Setup

Wir erstellen ein neues Spring Boot Projekt mit dem Spring Initializr und wählen als Sprache Kotlin aus. Für dieses Projekt benutzen wir Gradle mit Kotlin DSL als Build-Tool und die neueste Spring Boot 3.2 Version mit JDK 21.


Nachdem wir das Projekt erstellt haben, fügen wir noch weitere Bibliotheken als Abhängigkeiten hinzu:

```kotlin
implementation("com.open200:xesar-connect:1.0.0")
implementation("org.bouncycastle:bcprov-jdk18on:1.78")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
```

Wir benötigen Bouncy Castle als Security Provider, da Xesar-Connect auf die kryptografischen Funktionen von Bouncy Castle zurückgreift um eine mTLS-Verbindung mit Xesar’s MQTT-Broker aufzubauen.


Die Kotlin Coroutines Bibliotheken sind notwendig, um asynchrone Operationen von Xesar-Connect zu ermöglichen.


Wir löschen die application.properties Datei und erstellen stattdessen eine neue Datei application.yml im resources-Ordner. Wir fügen in der Datei nun den Pfad zum heruntergeladenen Konfigurations-ZIP als property xesarconnect.credentials-zip-path hinzu, damit wir später darauf zugreifen können:

```yaml
xesarconnect:
  credentials-zip-path: <path-to-user-config.zip>
```

Hello Xesar World

Um die Verbindung zur Xesar-Anlage herzustellen, muss Xesar-Connect initialisiert werden. Dafür erstellen wir eine Spring Configuration-Klasse XesarConnectConfig und verbinden eine Xesar-Connect Instanz mit der Xesar-Anlage:

```kotlin
@Configuration
class XesarConnectConfig {

    init {
        Security.addProvider(BouncyCastleProvider())
    }
    @Bean
    fun config(@Value("\${xesarconnect.credentials-zip-path}") zipPath: Path): Config {
        return Config.configureFromZip(configurationZipFile = zipPath).copy(logoutOnClose = false)
    }
    @Bean
    fun xesarConnect(config: Config): XesarConnect {
        return runBlocking {
            XesarConnect.connectAndLoginAsync(config, closeUponCoroutineCompletion = false).await()
        }
     }
}
```

 

Die Klasse bietet die XesarConnect Konfiguration in einer eigenen Bean vom Typ Config an. In der dazu gehörigen Methode config wird der zuvor festgelegte Pfad zur Konfigurations-ZIP Datei genutzt, um mit Hilfe von Config.configureFromZip die Konfiguration zu laden und zurückzugeben. Die Xesar-Connect-Konfiguration könnte auch direkt über die Config.configure Methode erfolgen, allerdings ist die Verwendung über die ZIP-Datei einfacher.


Die Methode XesarConnect nutzt die Config Bean um eine Verbindung zur Xesar Anlage aufzubauen und gibt ein XesarConnect Objekt zurück. Da der Verbindungsaufbau asynchron erfolgt, wird die Methode mit runBlocking ausgeführt und das Ergebnis mit await abgewartet.


Die zusätzlich übergebene Option closeUponCoroutineCompletion = false verhindert, dass die Verbindung nach Abschluss der Coroutine wieder geschlossen wird. Dies ist notwendig, da wir die Verbindung für die gesamte Laufzeit der Anwendung offen halten wollen.


Bevor wir loslegen mit dem eigentlichen Event-Listener, testen wir den Verbindungsaufbau und schreiben einen einfachen Logging-Listener, der die Topics aller Events in der Xesar-Anlage mitloggt:

```kotlin
@Component
class LoggingListener(private val xesar: XesarConnect) {

    private val log = LoggerFactory.getLogger(LoggingListener::class.java)

    @EventListener(ApplicationReadyEvent::class)
    suspend fun logTopics() {
        // only subscribe to all topics for testing purposes
        xesar.subscribeAsync(Topics(Topics.ALL_TOPICS)).await()
        xesar.on(AllTopicsFilter()) { log.info("Received message on {}", it.topic) }
    }
}
```

 

Wir nutzen Spring’s ApplicationReadyEvent um den Logging-Listener nach dem Start der Anwendung zu initialisieren. Die asynchrone Methode logTopics nutzt direkt die XesarConnect-Instanz, welche bereits eine aktive Verbindung zur Xesar-Anlage besitzt.


Mit subscribeAsync abonnieren wir alle Topics, auf denen in der Xesar-Anlage Events publiziert werden. Xesar-Connect erstellt in dabei im Hintergrund eine MQTT-Subscription. Damit wir auf die Events reagieren können, benötigen wir zusätzlich einen Event-Listener.


Dieser wird mit der Methode on registriert und bekommt einen Filter übergeben, der bestimmt, auf welche Events reagiert werden soll. In diesem Fall reagieren wir auf alle Events, indem wir den AllTopicsFilter verwenden. Der Event-Listener loggt den Topic des empfangenen Events.


Wenn man nun die Anwendung mit ./gradlew bootRun startet und Änderungen an der Xesar-Anlage vornimmt, werden die Events in der Konsole geloggt. Wenn man beispielsweise eine neue Person in der Anlage erstellt, wir das Topic xs3/1/ces/PersonCreated geloggt.


Battery Empty Event

Nachdem wir nun erfolgreich eine Verbindung zur Xesar-Anlage herstellen konnten und auch bereits Events empfangen können, wollen wir nun einen weiteren Event-Listener erstellen, der auf das Battery Empty Event reagiert.


Dieses Event wird ausgelöst, wenn die Batterie einer EVVA-Komponente (zum Beispiel eines EVVA-Zylinders) gewechselt werden sollte um einen reibungslosen Betrieb zu gewährleisten.


Da batteriebetriebene Komponenten, wie der EVVA-Zylinder, keine direkte Verbindung zum Netzwerk haben, ist zu beachten, dass das Battery Empty Event verzögert auf dem MQTT-Broker publiziert wird.


Beispielsweise wird das Event erst publiziert, wenn Zutritte mit dem Zylinder erfolgen und das Event über die Zutrittsmedien an die Anlage übermittelt wird. Wir erstellen dafür eine neue Klasse BatteryEmptyListener:

```kotlin
@Component
class BatteryEmptyListener(
    private val xesar: XesarConnect,
) {
    private val log = LoggerFactory.getLogger(LoggingListener::class.java)
    private val scope = CoroutineScope(Job() + Dispatchers.IO)
    private val batteryEmpty = Topics.Event.accessProtocolEventTopic(
            GroupOfEvent.MaintenanceComponent,
            EventType.BATTERY_EMPTY)

    @EventListener(ApplicationReadyEvent::class)
    suspend fun listenOnBatteryEmpty() {
        xesar.subscribeAsync(Topics(batteryEmpty)).await()

        xesar.on(TopicFilter(batteryEmpty)) {
            scope.launch {
                val event = AccessProtocolEvent.decode(it.message)
                log.warn(
                    "Battery warning received from xesar on installation point: {} ({})",
                    event.installationPointIdentifier,
                    event.installationPointName)
            }
        }    
    }   
}
```

Wie zuvor beim Logging-Listener nutzen wir Spring’s ApplicationReadyEvent um die Listener-Komponente nach dem Start der Anwendung zu initialisieren. Der Event-Listener abonniert nun das Topic für das Battery Empty Event.


Xesar-Connect bietet Hilfsmethoden wie hier Topics.Event.accessProtocolEventTopic um Topics für spezifische Events zu erstellen.


Obwohl der Logging-Listener bereits alle Topics abonniert hat, abonnieren wir hier explizit das Battery Empty Event, um den BatteryEmptyListener unabhängig vom Logging-Listener zu machen.


Der Event-Listener reagiert auf das Battery Empty Event, indem er eine Coroutine startet, die das empfangene Event deserialisiert und den Namen und die ID des betroffenen Einbauortes bzw. der betroffenen EVVA-Komponente loggt. Xesar-Connect bietet Klassen wie AccessProtocolEvent um einen einfachen und typisierten Zugriff auf Eventdaten zu ermöglichen.


Xesar-Connect ist einfach zu verwenden

Wir haben nun erfolgreich einen Event-Listener erstellt, der auf das Battery Empty Event reagiert. Dieser Event-Listener könnte nun um weitere Funktionalitäten erweitert werden, umbeispielsweise eine Benachrichtigung zu versenden oder eine Aktion auszuführen.


Es sollte noch beachtet werden, dass die Anwendung nicht für den produktiven Einsatz geeignet ist, da sie keine Fehlerbehandlung oder Tests enthält. Für den produktiven Einsatz sollte die Anwendung entsprechend erweitert werden.


Der Code von diesem Beispielprojekt ist auf GitHub verfügbar: Xesar Battery-Warning-Listener. Die Xesar-Schnittstelle bietet noch viele weitere Möglichkeiten, um auf Events zu reagieren, die Anlage zu konfigurieren oder Daten abzufragen. Mehr Details dazu finden Sie in der offiziellen Schnittstellendokumentation.

bottom of page