Letztens stieß sich eher zufällig auf ein Protokoll, welches mein Bastler-Herz höher schlagen lies: mqtt. Man nennt das kleine Pub/Sub (Publish/Subscribe) Protokoll auch das "Protokoll für das Internet der Dinge".

Kurz erklärt haben wir in der Mitte einen sogenannten "Broker", der die gesendeten (publizierten) Nachrichten von Clients entgegen nimmt und an Clients weiterreicht, die an diesem speziellen Thema interessiert sind (die Themen subscribed haben).

Hierbei geht es immer um relativ kleine Nachrichten (zum Beispiel GPS-Information, Temperatur, Helligkeitswerte,...), die mit geringem Overhead übertragen werden.
Anwendungsbeispiele sind ein Latitude-Eigenbau (vielleicht kennt jemand noch den begrabenen Google-Dienst "Latitude"), Temperatur-Sensoren, die via Netzwerk funken, Steckdosensteuerung und so weiter.

Publish/Subscribe funktioniert bei mqtt über hierarchische Topics.
Zum Beispiel habe ich für meine Temperatur-Sensoren die Topics temperatur/wohnzimmer, temperatur/kueche, etc und kann mit einem Client den Topic temperatur/# abonnieren, um sämtliche Temperaturwerte zu erhalten.
Oder ich habe die Topics latitude/mirco, latitude/alexander und latitude/eva, an die die jeweilige Personen ihre Geo-Daten senden. Auf einem Server könnte ich den Topic latitude/# abonnieren und in einer Web-Applikation auf einer Karte die Standorte aller Personen anzeigen.

Viele weitere Informationen und Skript-Schnipsel sind auf dieser Seite auf Englisch zu finden.

Im Folgenden geht es darum, einen mqtt-Broker – in diesem Fall mosquitto – auf einem Mac in Betrieb zu nehmen.
Wenn es gerne etwas größer sein darf und man mqtt für sein Unternehmen benötigt, lohnt es sich vielleicht auch, einen Blick auf HiveMQ zu werfen.

mosquitto Installieren: Homebrew

Der einfachste Installationsweg, der bei mir (fast) keine Zicken machte, war Ein Installationsweg ist über Homebrew, einen Package-Manager á la macports.

Falls noch nicht vorhanden kann Homebrew über

ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

installiert werden. Weitere Informationen zur Installation sind hier zu finden.

Zurück zu mosquitto:
Ein kleines Problemchen, dass Homebrew nicht alle Bibliotheken verlinken wollte, hatte ich dann doch. Um dies zu umgehen, hatte ich kurzfristig root zum Besitzer der homebrew-Binary erklärt, die Bibliotheken nochmals verlinken lassen und dann wieder mich selbst als Besitzer der homebrew-Binary gesetzt.

brew install mosquitto

# Ab hier wegen Bibliotheks-Link-Problemen bei der Installation
chown root:admin /usr/local/bin/brew
sudo brew link mosquitto
sudo chown apfelz:admin /usr/local/bin/brew

# Weg zum mosquitto-Stammverzeichnis
cd /usr/local/opt/mosquitto/

mosquitto Installieren: MacPorts

Über den Paketmanager MacPorts lässt sich mqtt ebenfalls installieren.
Falls noch nicht vorhanden, MacPorts herunterladen und installieren und dann mosquitto im Terminal mit folgendem Befehl installieren.

sudo port install mosquitto

Bitte beachten: Ich meinem Fall hatte ich damals homebrew verwendet, so können Pfadangaben in den folgenden Texten abweichen!

Zertifikat

Um eine gesicherte Verbindung zwischen Broker und (außerhalb des Heim-Netzwerk befindlichen) Clients herzustellen, wird eine verschlüsselte Verbindung empfohlen (wobei sie nicht unbedingt erforderlich ist).

Den mosquitto-Broker hatte ich so in 20 Minuten am Laufen. Für die TLS/Zertifikats-Problematik saß ich einen ganzen Sonntag fluchend vor'm Computer.

TLS-Zertifikat wie hier beschrieben erstellen.
Durch den in jenem Artikel beschriebenen Vorgang sollte man folgende Dateien in einem Verzeichnis – wir nehmen in diesem Fall

/usr/local/opt/mosquitto/etc/mosquitto/cert/
– haben:

openssl.conf
server.crt
server.csr
server.key

Die openssl.conf und server.csr interessieren uns nicht mehr, diese kann man löschen.

Ich stieß nun auf das Problem, dass ich jedes Mal den Pass-Code (der in der openssl.conf stand und den ich immer bei der Abfrage nach einem geheimen Schlüssel verwendete) angeben musste, wenn ich den mosquitto-Broker startete. Unschön.

Durch folgenden Befehl erstellte ich einen weiteren server.key, der unverschlüsselt ist, so dass mich mosquitto beim Starten nicht immer danach frägt.

sudo openssl rsa -in server.key -out server2.key

...denn sie wissen nicht, was sie tun! Ich hatte aber einen ganze Sonntag nach einer Möglichkeit gesucht, diesen Zertifikatsmist richtig zu erstellen, eine .pem-Datei zu haben, wie sie laut mosquitto-manual verlangt wird; aber nichts funktionierte.
Ich weiß nicht, ob diese Lösung nun die Beste und überhaupt besonders sicher ist, aber immerhin funktioniert sie und ich muss für's Erste nicht mehr weiter fluchen.

Laut mosquitto-manual braucht man auch noch ein weiteres Zertifikat, welches man hier herunterladen kann und welches später in der Konfiguration als cafile hinterlegt wird.
Keine Ahnung, ob das auch tatsächlich das richtige Zertifikat ist, oder ob ich doch eher das CAcert-Root-Zertifikat verwenden sollte (da ich mein Zertifikat über CAcert.org erstellt hatte). Es funktioniert immerhin mit dem von mosquitto vorgeschlagenen Zertifikat.

Konfiguration

Meine mosquitto.conf:

sudo pico /usr/local/opt/mosquitto/etc/mosquitto/mosquitto.conf

(Eine ziemlich lange Konfigurations-Datei mit vielen Informationen. Folgende Zeilen habe ich an das Ende der Datei hinzugefügt. Die Original-Werte habe ich auskommentiert gelassen)

listener 1883 # Unverschlüsselte Verbindung. Sofern auf der Firewall nicht durchgeschleust nur für's heimische Netzwerk
listener 8883 # Verschlüsselte Verbindung für Clients über Port 8883 (kann in der Firewall durchgereicht werden)
cafile /usr/local/opt/mosquitto/etc/mosquitto/cert/ca-bundle.crt   # Von startssl.com: http://www.startssl.com/certs/ca-bundle.crt
certfile /usr/local/opt/mosquitto/etc/mosquitto/cert/ca.crt        # Via CAcerts.org erstelltes Zertifikat
keyfile /usr/local/opt/mosquitto/etc/mosquitto/cert/server2.key    # Den eben erstellten Schlüssel
tls_version tlsv1
require_certificate false
allow_anonymous false
password_file mosquitto.pass
user apfelz #Eigener Benutzername; bzw. Benutzer unter dem der mosquitto-Prozess laufen soll

Die Passwort-Datei, in der die Benutzer und Passworte stehen, die sich als Client verbinden dürfen, müssen wir erst noch erstellen:

cd /usr/local/opt/mosquitto/etc/mosquitto/
sudo mosquitto_passwd -c mosquitto.pass apfelz

Passwort für Benutzer apfelz eingeben und bestätigen.
Für weitere Benutzer ohne die Option -c wiederholen.

mosquitto-Server starten

Einfacher Start auf der Kommandozeile:

cd /usr/local/opt/mosquitto/
sbin/mosquitto -c etc/mosquitto/mosquitto.conf

Es sollten Informationen angezeigt werden, dass mosquitto auf den Ports 1883 und 8883 läuft.

Publish und Subscribe

Vom Server selbst aus kann man Topics abonnieren und Informationen an Topics senden. Die dazu benötigten Programme mosquitto_sub und mosquitto_pub wurden mit mosquitto installiert.

Ein kleiner Test: Wir starten eine subscribe-Session und abonnieren das Topic test:

mosquitto_sub -u apfelz -P dasApfelzPasswort -t test

Die Adresse des Servers und den Port muss man in diesem Fall nicht angeben, da mosquitto_sub sich, wenn nicht anders angegeben, mit localhost auf Port 1883 verbindet.

Nun öffnen wir ein weiteres Terminal-Fenster und veröffentlichen etwas in den test-Topic

mosquitto_pub -u apfelz -P dasApfelzPasswort -t test -m "Dies ist ein Test"

Im ersten Terminal-Fenster sollte nun sofort eine neue Zeile "Dies ist ein Test" auftauchen.

Topics haben bei mqtt Hierarchien, über die ich verschiedene Themen zusammen abonnieren kann.

mosquitto_pub -u apfelz -P dasApfelzPasswort -t test/apfelz/client1   -m "Dies ist ein Test"
mosquitto_pub -u apfelz -P dasApfelzPasswort -t test/apfelz/client2   -m "Dies ist ein Test"
mosquitto_pub -u apfelz -P dasApfelzPasswort -t test/mircoweb/client1 -m "Dies ist ein Test"

# Empfängt alle oben veröffentlichten Meldungen
mosquitto_sub -u apfelz -P dasApfelzPasswort -t test/#

# Empfängt nur Meldungen des apfelz-Sub-Topics
mosquitto_sub -u apfelz -P dasApfelzPasswort -t test/apfelz/#

# Empfängt nur Meldungen der client1'er
mosquitto_sub -u apfelz -P dasApfelzPasswort -t test/+/client1

Das + ist ein Platzhalter für genau eine Hierarchie-Ebene, das # ist ein Platzhalter für beliebig viele Hierarchieebenen.

Hierarchien kann man sich wie Ordner einer Verzeichnis-Struktur vorstellen, wobei die Struktur in diesem Fall nicht im Vornherein erstellt werden muss.

Problem beim Benutzen eines Topic-Platzhalters:
Man weiß nicht, unter welchem Topic die einkommenden Meldungen publiziert wurden.

mosquitto_pub -u apfelz -P dasApfelzPasswort -t test/apfelz/client1   -m "Mir geht es gut"
mosquitto_pub -u apfelz -P dasApfelzPasswort -t test/apfelz/client2   -m "Mir ist schlecht"

mosquitto_sub -u apfelz -P dasApfelzPasswort -t test/apfelz/#
#Mir geht es gut
#Mir ist schlecht

Ich dachte erst, dass es doch nicht möglich sein kann, dass man an dieser Stelle nicht die Herkunft der Nachricht anzeigen kann, wurde auf den manpages allerdings nicht fündig.
Lösung dieses "Problemes" ist es, mosquitto_sub mit der Option

-v
aufzurufen, die ich als einfaches "verbose" überlesen hatte.
Der ursprüngliche Topic wird so nun durch ein Leerzeichen getrennt vom eigentlichen Payload ausgegeben:

mosquitto_sub -u apfelz -P dasApfelzPasswort -t test/apfelz/# -v
#client1 Mir geht es gut
#client2 Mir ist schlecht

Autostart

Damit mosquitto sich sowohl bei einem Neustart wieder startet als auch ohne offenes Terminal-Fenster im Hintergrund läuft, habe ich folgende .plist-Datei erstellt und in /Library/LaunchAgents/ gespeichert.

Man sollte beachten:
Wenn man einen systemweiten LaunchAgent-Job in /Library/LaunchAgents/ erstellt, versucht mosquitto als Benutzer mosquitto zu laufen. Dies wird nicht funktionieren, da auf dem System sehr wahrscheinlich kein derartiger Benutzer angelegt ist.

Es gibt da folgende Möglichkeiten:

  • In der Konfigurations-Datei mosquitto.conf die Zeile
    user apfelz
    hinzufügen (mit dem eigenen Benutzernamen; dieser ist über den Befehl
    whoami
    in Erfahrung zu bringen)
  • In der Konfigurations-Datei mosquitto.conf die Zeile
    user 
    ohne Benutzernamen hinzuzufügen. Dann läuft mosquitto als root. Dies wird allerdings nicht empfohlen
  • Als privater LaunchAgent laufen lassen; also die folgende .plist im Verzeichnis ~/Library/LaunchAgents/ ablegen

de.apfelz.mosquitto.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>KeepAlive</key>
        <true/>
        <key>Label</key>
        <string>de.apfelz.mosquitto</string>
        <key>ProgramArguments</key>
        <array>
                <string>/usr/local/opt/mosquitto/sbin/mosquitto</string>
                <string>-d</string>
                <string>-c</string>
                <string>/usr/local/opt/mosquitto/etc/mosquitto/mosquitto.conf</string>
        </array>
        <key>RunAtLoad</key>
        <true/>
        <key>StandardErrorPath</key>
        <string>/Library/Logs/apfelz/mosquitto.log</string>
        <key>StandardOutPath</key>
        <string>/Library/Logs/apfelz/mosquitto.log</string>
</dict>
</plist>

Mit
launchctl load /Library/LaunchAgents/de.apfelz.mosquitto.plist
starten.