Rauchmelder mit Visual Basic .NET auslesen

Für diese Projekt setzen wir voraus, dass eine Visual Basic .NET Entwicklungsumgebung eingerichtet ist und ein grundsätzliches Verständnis der Visual Basic .NET Programmiersprache vorhanden ist.

Falls dies nicht der Fall ist sollte hier begonnen werden. Informationen über die Tinkerforge API sind dann hier zu finden.

Wir setzen weiterhin voraus, dass ein passender Rauchmelder mit einem Industrial Digital In 4 Bricklet verbunden wurde wie hier beschrieben.

Ziele

Wir setzen uns folgendes Ziel für dieses Projekt:

  • Alarmstatus eines Rauchmelders auslesen
  • und auf dessen Alarmsignal reagieren.

Da dieses Projekt wahrscheinlich 24/7 laufen wird, wollen wir sicherstellen, dass das Programm möglichst robust gegen externe Einflüsse ist. Das Programm sollte weiterhin funktionieren falls

  • Bricklets ausgetauscht werden (z.B. verwenden wir keine fixen UIDs),
  • Brick Daemon läuft nicht oder wird neu gestartet,
  • WIFI Extension ist außer Reichweite oder
  • Brick wurde neu gestartet (Stromausfall oder USB getrennt).

Im Folgenden werden wir Schritt für Schritt zeigen wie diese Ziele erreicht werden können.

Schritt 1: Bricks und Bricklets dynamisch erkennen

Als Erstes legen wir fest wohin unser Programm sich verbinden soll:

Const HOST As String = "localhost"
Const PORT As Integer = 4223

Falls eine WIFI Extension verwendet wird, oder der Brick Daemon auf einem anderen PC läuft, dann muss "localhost" durch die IP Adresse oder den Hostnamen der WIFI Extension oder des anderen PCs ersetzt werden.

Nach dem Start des Programms müssen der EnumerateCallback Callback und der Connected Callback registriert und ein erstes Enumerate ausgelöst werden:

Sub Main()
    ipcon = New IPConnection()
    ipcon.Connect(HOST, PORT)

    AddHandler ipcon.EnumerateCallback, AddressOf EnumerateCB
    AddHandler ipcon.Connected, AddressOf ConnectedCB

    ipcon.Enumerate()
End Sub

Der Enumerate Callback wird ausgelöst wenn ein Brick per USB angeschlossen wird oder wenn die Enumerate() Funktion aufgerufen wird. Dies ermöglicht es die Bricks und Bricklets im Stapel zu erkennen ohne im Voraus ihre UIDs kennen zu müssen.

Der Connected Callback wird ausgelöst wenn die Verbindung zur WIFI Extension oder zum Brick Daemon hergestellt wurde. In diesem Callback muss wiederum ein Enumerate angestoßen werden, wenn es sich um ein Auto-Reconnect handelt:

Sub ConnectedCB(ByVal sender As IPConnection, ByVal connectedReason as Short)
    If connectedReason = IPConnection.CONNECT_REASON_AUTO_RECONNECT Then
        ipcon.Enumerate()
    End If
End Sub

Ein Auto-Reconnect bedeutet, dass die Verbindung zur WIFI Extension oder zum Brick Daemon verloren gegangen ist und automatisch wiederhergestellt werden konnte. In diesem Fall kann es sein, dass die Bricklets ihre Konfiguration verloren haben und wir sie neu konfigurieren müssen. Da die Konfiguration beim Enumerate (siehe unten) durchgeführt wird, lösen wir einfach noch ein Enumerate aus.

Schritt 1 zusammengefügt:

Module SmokeDetector
    Const HOST As String = "localhost"
    Const PORT As Integer = 4223

    Sub ConnectedCB(ByVal sender As IPConnection, ByVal connectedReason as Short)
        If connectedReason = IPConnection.CONNECT_REASON_AUTO_RECONNECT Then
            ipcon.Enumerate()
        End If
    End Sub

    Sub Main()
        ipcon = New IPConnection()
        ipcon.Connect(HOST, PORT)

        AddHandler ipcon.EnumerateCallback, AddressOf EnumerateCB
        AddHandler ipcon.Connected, AddressOf ConnectedCB

        ipcon.Enumerate()
    End Sub
End Module

Schritt 2: Bricklets beim Enumerate initialisieren

Während des Enumerierungsprozesses soll das Industrial Digital In 4 Bricklet konfiguriert werden. Dadurch ist sichergestellt, dass es neu konfiguriert wird nach einem Verbindungsabbruch oder einer Unterbrechung der Stromversorgung.

Die Konfiguration soll beim ersten Start (ENUMERATION_TYPE_CONNECTED) durchgeführt werden und auch bei jedem extern ausgelösten Enumerate (ENUMERATION_TYPE_AVAILABLE):

Sub EnumerateCB(ByVal sender As IPConnection, ByVal uid As String, _
                ByVal connectedUid As String, ByVal position As Char, _
                ByVal hardwareVersion() As Short, ByVal firmwareVersion() As Short, _
                ByVal deviceIdentifier As Integer, ByVal enumerationType As Short)
    If enumerationType = IPConnection.ENUMERATION_TYPE_CONNECTED Or _
       enumerationType = IPConnection.ENUMERATION_TYPE_AVAILABLE Then

Das Industrial Digital In 4 Bricklet wird so eingestellt, dass es die InterruptCB Callback-Funktion aufruft wenn sich die Spannung an einem der Eingänge verändert. Die Entprellperiode wird auf 10s (10000ms) gestellt, um zu vermeiden zu viele Callback zu erhalten. Interrupt-Erkennung wird für alle Eingänge aktiviert (15 = 0b1111).

If deviceIdentifier = BrickletIndustrialDigitalIn4.DEVICE_IDENTIFIER Then
    brickletIndustrialDigitalIn4 = New BrickletIndustrialDigitalIn4(UID, ipcon)
    brickletIndustrialDigitalIn4.SetDebouncePeriod(10000)
    brickletIndustrialDigitalIn4.SetInterrupt(15)
    AddHandler brickletIndustrialDigitalIn4.Interrupt, AddressOf InterruptCB
End If

Schritt 2 zusammengefügt:

Sub EnumerateCB(ByVal sender As IPConnection, ByVal uid As String, _
                ByVal connectedUid As String, ByVal position As Char, _
                ByVal hardwareVersion() As Short, ByVal firmwareVersion() As Short, _
                ByVal deviceIdentifier As Integer, ByVal enumerationType As Short)
    If enumerationType = IPConnection.ENUMERATION_TYPE_CONNECTED Or _
       enumerationType = IPConnection.ENUMERATION_TYPE_AVAILABLE Then
        If deviceIdentifier = BrickletIndustrialDigitalIn4.DEVICE_IDENTIFIER Then
            brickletIndustrialDigitalIn4 = New BrickletIndustrialDigitalIn4(UID, ipcon)
            brickletIndustrialDigitalIn4.SetDebouncePeriod(10000)
            brickletIndustrialDigitalIn4.SetInterrupt(15)
            AddHandler brickletIndustrialDigitalIn4.Interrupt, AddressOf InterruptCB
        End If
    End If
End Sub

Schritt 3: Auf Alarmsignal reagieren

Jetzt müssen wir noch auf das Alarmsignal des Rauchmelders reagieren. Es soll aber nur auf das Einschalten der LED reagiert werden, nicht auf das Ausschalten. Dazu wird valueMask auf > 0 geprüft, in diesem Fall liegt an mindesten einem Eingang eine Spannung an, sprich die LED leuchtet.

Sub InterruptCB(ByVal sender As BrickletIndustrialDigitalIn4, _
                ByVal interruptMask As Integer, ByVal valueMask As Integer)
    If valueMask > 0 Then
        System.Console.WriteLine("Fire! Fire!")
    End If
End Sub

Das ist es. Wenn wir diese drei Schritte zusammen in eine Datei kopieren und ausführen, dann hätten wir jetzt eine funktionierendes Programm, das den Alarmstatus eines Rauchmelders ausließt und auf dessen Alarmsignal reagiert.

In der jetzigen Form gibt das Programm nur eine Meldung aus. Dies kann auf verschiedene Weise verbessert werden. Zum Beispiel könnte das Programm jemanden per E-Mail oder SMS über den Alarm informieren.

Wie dem auch sei, wir haben noch nicht alle Ziele erreicht. Das Programm ist noch nicht robust genug. Was passiert wenn die Verbindung beim Start des Programms nicht hergestellt werden kann, oder wenn das Enumerate nach einem Auto-Reconnect nicht funktioniert?

Wir brauchen noch Fehlerbehandlung!

Schritt 4: Fehlerbehandlung und Logging

Beim Start des Programms versuchen wir solange die Verbindung herzustellen, bis es klappt:

while True
    Try
        ipcon.Connect(HOST, PORT)
        Exit While
    Catch e As System.Net.Sockets.SocketException
        System.Console.WriteLine("Connection Error: " + e.Message)
        System.Threading.Thread.Sleep(1000)
    End Try
End While

und es wird solange versucht ein Enumerate zu starten bis auch dies geklappt hat:

while True
    try
        ipcon.Enumerate()
        Exit While
    Catch e As NotConnectedException
        System.Console.WriteLine("Enumeration Error: " + e.Message)
        System.Threading.Thread.Sleep(1000)
    End Try
End While

Mit diesen Änderungen kann das Programm schon gestartet werden bevor der Master Brick angeschlossen ist.

Es müssen auch noch mögliche Fehler während des Enumerierungsprozesses behandelt werden:

If deviceIdentifier = BrickletIndustrialDigitalIn4.DEVICE_IDENTIFIER Then
    Try
        brickletIndustrialDigitalIn4 = New BrickletIndustrialDigitalIn4(UID, ipcon)
        brickletIndustrialDigitalIn4.SetDebouncePeriod(10000)
        brickletIndustrialDigitalIn4.SetInterrupt(15)
        AddHandler brickletIndustrialDigitalIn4.Interrupt, AddressOf InterruptCB
        System.Console.WriteLine("Industrial Digital In 4 initialized")
    Catch e As TinkerforgeException
        System.Console.WriteLine("Industrial Digital In 4 init failed: " + e.Message)
        brickletIndustrialDigitalIn4 = Nothing
    End Try
End If

Zusätzlich wollen wir noch ein paar Logausgaben einfügen. Diese ermöglichen es später herauszufinden was ein mögliches Problem ausgelöst hat.

Zum Beispiel, wenn der Master Brick über WLAN angebunden ist und häufig Auto-Reconnects auftreten, dann ist wahrscheinlich die WLAN Verbindung nicht sehr stabil.

Schritt 5: Alles zusammen

Jetzt sind alle für gesteckten Ziele für unseren gehackten Rauchmelder erreicht.

Das gesamte Programm für den gehackten Rauchmelder (download):

Imports Tinkerforge

Module SmokeDetector
    Const HOST As String = "localhost"
    Const PORT As Integer = 4223

    Private ipcon As IPConnection = Nothing
    Private brickletIndustrialDigitalIn4 As BrickletIndustrialDigitalIn4 = Nothing

    Sub InterruptCB(ByVal sender As BrickletIndustrialDigitalIn4, _
                    ByVal interruptMask As Integer, ByVal valueMask As Integer)
        If valueMask > 0 Then
            System.Console.WriteLine("Fire! Fire!")
        End If
    End Sub

    Sub EnumerateCB(ByVal sender As IPConnection, ByVal uid As String, _
                    ByVal connectedUid As String, ByVal position As Char, _
                    ByVal hardwareVersion() As Short, ByVal firmwareVersion() As Short, _
                    ByVal deviceIdentifier As Integer, ByVal enumerationType As Short)
        If enumerationType = IPConnection.ENUMERATION_TYPE_CONNECTED Or _
           enumerationType = IPConnection.ENUMERATION_TYPE_AVAILABLE Then
            If deviceIdentifier = BrickletIndustrialDigitalIn4.DEVICE_IDENTIFIER Then
                Try
                    brickletIndustrialDigitalIn4 = New BrickletIndustrialDigitalIn4(UID, ipcon)
                    brickletIndustrialDigitalIn4.SetDebouncePeriod(10000)
                    brickletIndustrialDigitalIn4.SetInterrupt(15)
                    AddHandler brickletIndustrialDigitalIn4.Interrupt, AddressOf InterruptCB
                    System.Console.WriteLine("Industrial Digital In 4 initialized")
                Catch e As TinkerforgeException
                    System.Console.WriteLine("Industrial Digital In 4 init failed: " + e.Message)
                    brickletIndustrialDigitalIn4 = Nothing
                End Try
            End If
        End If
    End Sub

    Sub ConnectedCB(ByVal sender As IPConnection, ByVal connectedReason as Short)
        If connectedReason = IPConnection.CONNECT_REASON_AUTO_RECONNECT Then
            System.Console.WriteLine("Auto Reconnect")
            while True
                Try
                    ipcon.Enumerate()
                    Exit While
                Catch e As NotConnectedException
                    System.Console.WriteLine("Enumeration Error: " + e.Message)
                    System.Threading.Thread.Sleep(1000)
                End Try
            End While
        End If
    End Sub

    Sub Main()
        ipcon = New IPConnection()
        while True
            Try
                ipcon.Connect(HOST, PORT)
                Exit While
            Catch e As System.Net.Sockets.SocketException
                System.Console.WriteLine("Connection Error: " + e.Message)
                System.Threading.Thread.Sleep(1000)
            End Try
        End While

        AddHandler ipcon.EnumerateCallback, AddressOf EnumerateCB
        AddHandler ipcon.Connected, AddressOf ConnectedCB

        while True
            try
                ipcon.Enumerate()
                Exit While
            Catch e As NotConnectedException
                System.Console.WriteLine("Enumeration Error: " + e.Message)
                System.Threading.Thread.Sleep(1000)
            End Try
        End While

        System.Console.WriteLine("Press key to exit")
        System.Console.ReadLine()
        ipcon.Disconnect()
    End Sub
End Module