SIMATIC S7-1200/1500 als MQTT Publisher (Teil 1)
MQTT eignet sich nicht nur besonders gut für eigenständige Sensoren oder embedded Geräte, um Daten in die Cloud zu schicken, auch komplette Steuerungssysteme können MQTT implementieren und so Prozessdaten über die Cloud austauschen.
In diesem Beitrag wollen wir MQTT auf einer Siemens SIMATIC S7-1200 / S7-1500 implementieren und Prozessdaten als Publisher an einen MQTT Server senden. In diesem Beispiel konfigurieren wir uns einen eigenen, lokalen MQTT Server (aka. MQTT Broker). Die zugehörige Client-Implementierung auf der SIMATIC kann aber analog auch mit jedem anderen MQTT Server in der Cloud verwendet werden. Bevor ihr MQTT auf eurer eigenen SIMATIC implementiert, empfiehlt es sich einen Blick in den Aufbau und die Funktionsweise des MQTT Protokolls zu werfen.
Projektübersicht und Netzarchitektur
Als Hardware für dieses Tutorial verwenden wir unseren SPS / PLC Trainer, der über verschiedene Sensoren, Aktuatoren und zwei SIMATIC S7 Steuerungen verfügt. Als Hauptsteuerung verwenden wir die S7-1511, auf der im Laufe des Projektes auch der MQTT Publisher implementiert wird. Der MQTT Broker sowie der MQTT Subscriber wird (aus Gründen der Einfachheit) auf dem Entwickler-Notebook installiert, konfiguriert und ausgeführt. Die gesamte Systemarchitektur kann aus dem nachfolgenden Diagramm entnommen werden.
Lokalen MQTT Broker einrichten - mosquitto
Eclipse Mosquitto ist ein open source MQTT Message Broker, der die MQTT Versionen 5.0, 3.1.1 und 3.1. unterstützt. Installiert werden kann er auf Windows, Linux und macOS. In unserem Beispiel werden wir mosquitto auf einem Debian Linux installieren (das in meinem Fall virtualisiert auf WSL2 läuft). Die Installation erfolgt über apt-get, wir installieren den Broker und einen CLI Client:
# update repo and install
apt-get update && apt-get upgrade
apt-get install mosquitto mosquitto-clients
# start mosquitto service
sudo service mosquitto start
# check if service is running
sudo service mosquitto status
# stop mosquitto service to perform config change
sudo service mosquitto stop
Als nächstes passen wir die Konfiguration des mosquitto Brokers so an, sodass dieser nur Clients akzeptiert, die sich mit einem Benutzernamen und Passwort authentifizieren. Die Verschlüsselung über SSL wird auch unterstützt, dies ist jedoch nicht Teil dieses Beitrags und wird in einem weiteren Tutorial separat behandelt.
In diesem Tutorial muss sich der MQTT Client zwar beim Server mittels Benutzername und Passwort authentifizieren, die Datenübertragung findet trotzdem unverschlüsselt statt.
Alle Konfigurationsdateien werden im Ordner /etc/mosquitto/
abgelegt. Als erstes erstellen wir ein File mit dem Benutzernamen und Passwort für den MQTT Client - unsere SIMATIC S7-1500 Steuerung. Es können sich gleichzeitig mehrer Clients mit identischem Username und Passwort authentifizieren und kommunizieren, nur die Client ID der jeweiligen Clients muss unterschiedlich sein (aus Sicht der IT-Sicherheit erscheint es wenig sinnvoll, den gleichen Benutzer für unterschiedliche / mehrere Clients zu verwenden, in einem Produktivsystem sollte darauf geachtet werden).
Mit folgendem Code Snippet (1) wird der Benutzer simsiincosuser angelegt, indem in der Datei passwd (vollständiger Pfad: /etc/mosquitto/passwd
) zu seinem Benutzernamen simsiincosuser ein gehashtes Passwort abgelegt wird. Mit dem zweiten Befehl (2) wird die Default-Config in Nano zum Editieren geöffnet.
# 1. File passwd mit Username 'simsiincosuser' und hashed Passwort erstellen in /etc/mosquitto
mosquitto_passwd -c /etc/mosquitto/passwd simsiincosuser
# 2. mosquitto config File zum Bearbeiten öffnen
nano /etc/mosquitto/conf.d/default.conf
# 3. mosquitto broker/server neu starten
sudo service mosquitto restart
Jetzt müssen wir die Config Datei öffnen und folgende Eigenschaften festlegen. Über allow_anonymous false
wird eingestellt, dass eine Authentication über Benutzername und Passwort zwingend notwendig ist und anonyme Benutzer abgelehnt werden. Mittels password_file
geben wir an, wo mosquitto nach der Datei mit den registrierten Usern schauen soll. Mit Ctrl+O und Ctrl+X wird die Config abgespeichert und der Editor geschlossen. Im Anschluss daran kann mit (3) der mosquitto Service neu gestartet werden, die geänderte Config wird sofort übernommen.
Lokalen MQTT Broker testen
Jetzt ist der lokale Broker eingerichtet, sodass wir einen Funktionstest durchführen können. Dazu nehmen wir das kürzlich installierte CLI Tool (mosquitto-clients) zur Hand und gehen folgendermaßen vor:
- Sicherstellen, dass der MQTT Broker / Server läuft
- Im Terminal den Subscriber unter einem frei wählbaren Topic starten (-d für daemonized, -u für den Username und -P für das zugehörige Passwort, wie im vorigen Abschnitt eingerichtet)
- In einem weiteren Terminal mit dem Publisher in das vom Subscriber abonnierte Topic posten
- Beim Subscriber prüfen, ob die Nachricht eingegangen ist
# Sicherstellen, dass der MQTT Broker / Server läuft (1)
sudo service mosquitto restart
# Im Terminal den Subscriber unter einem frei wählbaren Topic starten (2)
mosquitto_sub -d -u simsiincosuser -P s71500siincosmqtt -t /controller1/data
# In einem weiteren Terminal mit dem Publisher in das vom Subscriber abonnierte Topic posten (3)
mosquitto_pub -d -h localhost -u simsiincosuser -P s71500siincosmqtt -t /controller1/data -m "Hello World Again!"
# Beim Subscriber prüfen, ob die Nachricht eingegangen ist (4)
MQTT auf der SIMATIC S7-1500 installieren
Um nun mit unserer SIMATIC Nachrichten auf unseren MQTT Server (oder einen anderen MQTT Cloud Dienst) pushen zu können, müssen wir als aller erstes in unser aktuelles TIA Projekt die MQTT Bibliothek von Siemens importieren. Die Bibliothek ist lauffähig auf der SIMATIC S7-1200 und SIMATIC S7-1500, für den Download benötigt ihr einen Siemens Industry Support Account (zum Download) und müsst dann nur noch die passende Version für euer TIA Portal laden. Die Bibliothek heißt Libraries_Comm_Controller und beinhaltet noch weitere Kommunikationsmodule, auf die wir in diesem Beitrag nicht weiter eingehen werden. Auch die offizielle Dokumentation von Siemens für die Bibliothek ist dort zu finden, ein Blick hinein lohnt sich auf jeden Fall.
Nachdem der Download abgeschlossen ist, den Inhalt des ZIP-Archives entpacken, damit dieser dann im TIA Portal geöffnet werden kann. Dazu in TIA das eigene Projekt öffnen, über Libraries (1) -> Global libraries (2) -> Open Global Library (3) die Bibliothek als Global Library importieren und den Baum unter MQTT aufklappen (siehe Screenshot). Damit ist die Bibliothek erstmal im TIA Portal importiert. Im nächsten Schritt importieren wir diese in unser aktuelles Softwareprojekt.
Dazu klappen wir erstmal links in unserem Project Tree den Baum unterhalb der Steuerung auf, in der wir den MQTT Client nutzen möchten, sodass alle Program Blocks sichtbar werden. Um nun die MQTT Bibliothek in unser Projekt zu importieren, ziehen wir per Drag'n Drop die MQTT Bibliothek aus der globalen Bibliotheksverwaltung in unsere Program Blocks (siehe Screenshot). Im Anschluss sehen wir die Bibliothek (sowie ihren gesamten Baum) in unserem Projekt in den Program Blocks und die zugehörigen Datentypen in unserem Projekt unter PLC data types. Die Bibliothek haben wir erfolgreich in unser Softwareprojekt importiert und können nun mit der eigentlichen Programmierung unseres Clients starten.
MQTT auf der SIMATIC Konfigurieren
Da nun der Import abgeschlossen ist, können wir mit der eigentlichen Programmierung und Parametrierung des MQTT Publishers beginnen. Dazu müssen wir als aller erstes einen Datenblock mit einer ganzen Menge an Parametern anlegen, um diese im nächsten Schritt an die MQTT Bibliothek zu übergeben. Den Datenblock habe ich mqttdb genannt und wie im folgenden Screenshot zu sehen mit den entsprechenden Parametern befüllt.
Im nächsten Schritt erstellen wir eine Funktion, die den Publish Vorgang übernimmt und dazu von der Main (OB1) zyklisch aufgerufen werden muss. Bei mir heißt diese Funktion mqtt_pub und implementiert LMQTT_Client_Pub aus der Siemens MQTT Bibliothek. Dazu ziehen wir - wie im nächsten Screenshot zu sehen - den Funktionsblock aus der Bibliothek in den Editor. Der Editor zeigt uns jetzt, welche Parameter der Aufruf benötigt. Diese referenzieren wir einfach auf den von uns vorhin angelegten Datenblock mqttdb.
MQTT Client Konfiguration für den Endbenutzer
Die Parametrierung des MQTT Publishers ist damit abgeschlossen und wir können mit der Serverkonfiguration und dem Scheduling der Nutzdaten fortfahren. Aus Sicht des Maschinenherstellers - der unsere Steuerung samt MQTT Publisher in seiner Maschine verbaut und seinem Endkunden ausliefert - scheint es sinnvoll, bestimmte MQTT-Einstellungen dem Endanwender zu überlassen. Dieser kann dann z.B. seinen eigenen MQTT Server verwenden, oder einen beliebigen Cloud Dienst nutzen. Dazu müssen wir lediglich bestimmte MQTT-Einstellungen offen legen, sodass diese - z.B. am Touchpanel der Maschine - angepasst werden können. Die in den SIMATIC Basic Panels verfügbaren Widgets reichen dafür auch aus, sodass mit wenig Aufwand eine entsprechende View mit MQTT-Einstellungen erstellt werden kann.
Um dem Endanwender die Möglichkeit zu geben seinen eigenen MQTT Broker (Server) nutzen zu können, legen wir folgende Variablen auf das Touchpanel, damit dieser diese komfortabel an seine Infrastruktur anpassen kann:
- Client ID: diese muss unique für jeden Client am Broker sein, da sich immer nur ein Client mit der ID am Server registrieren kann
- IP Adresse: die IP Adresse des Brokers, die Bibliothek von Siemens erlaubt auch die Eingabe einer Domain, aufgrund des lokalen Testaufbaus habe ich jedoch mit der IP Adresse gearbeitet
- Benutzer: den Benutzername, den wir vorher am MQTT Broker eingerichtet haben
- Passwort: das zugehörige Passwort, das wir vorher am MQTT Broker eingerichtet haben
- Topic: das Topic unter dem unsere Maschine/Steuerung die Prozessdaten an den Broker pushen soll
- Mit dem Switch "Publisher" wird der Client aktiviert oder deaktiviert - er stoppt oder startet den Push an den Server
Damit diese Daten dann in der Steuerung an der richtigen Stelle landen, habe ich einen zusätzlichen Datenblock "HMI_Interchange" angelegt, der die Variablen mit dem HMI synchronisiert. Die zugehörige Funktion "syncHMI" synchronisiert mir die Daten aus dem HMI (KTP 400 Basic Touchpanel) an die benötigte Stelle in der Steuerung (syncHMI() wird zyklisch aufgerufen). In diesem Fall werden so die MQTT Parameter in der Funktion "syncHMI" den Variablen im Datenblock "mqttdb" zugeordnet.
Theoretisch hätte man die HMI Variablen direkt auf die entsprechenden MQTT Variablen im "mqttdb" Datenblock mappen können. Ich finde jedoch die Möglichkeit die Nutzereingaben vorher filtern zu können recht charmant, da man so mehr Kontrolle über die Nutzereingaben hat. Man kann z.B. auf gewisse Zeichenfolgen reagieren und diese ablehnen, außerdem hat man alle vom Nutzer einzugebenden Daten zentral in einem Datenblock organisiert.
Setup testen
Da wir nun soweit alle Einstellungen fertig haben, können wir unser Setup testen, sowohl die Konfiguration des Broker als auch unsere Implementierung auf der SIMATIC. Der Test beschränkt sich erstmal darauf, einen statischen String als Message an unseren MQTT Server zu pushen. Das Topic holen wir uns aus den Nutzereingaben ("HMI_Interchange".mqtt.topic, wie im vorigen Abschnitt und Screenshot gezeigt), die Message schreiben wir zu Testzwecken erstmal hardcoded in unser Programm.
Um zyklisch Nachrichten pushen zu können, aktivieren wir nun die "Clock memory bits", da wir diese als zyklischen Trigger nutzen können. Sobald "mqttdb".control.publish
eine steigende Flanke (rising edge) bekommt, wird der Push der aktuellen Nachricht, die sich in "mqttdb".message
befindet, getriggert und ausgeführt.
Die Codezeile, um alle zwei Sekunden eine Nachricht abzusetzen könnte z.B. so aussehen:"mqttdb".control.publish := "Clock_0.5Hz" AND "HMI_Interchange".mqtt.enabled;
Die logische UND-Kombination aus Taktgeber und Benutzereingabe sorgt dafür, dass nur gepusht wird, wenn der Benutzer auch wirklich den MQTT Client über das HMI aktiviert hat.
Um anfänglich nur die Funktion unseres Publishers zu prüfen, senden wir alle zwei Sekunden eine Message, die hardcoded bei uns im Quellcode steht: '{"demovalue1":22.4}'
Da die MQTT Implementierung keinen String als Nachricht akzeptiert ("mqttdb".message[]), sondern ein Array of Byte, müssen wir als aller erstes den String in das Array einlesen. Dazu können wir die Konvertierungsfunktion Strg_TO_Chars(Strg, pChars, Cnt, Chars)
nutzen. Diese nimmt als Parameter den String in Strg entgegen, in pChars den Index, ab dem das Array beschrieben werden soll, gibt über Cnt die geschriebene Anzahl (Länge) zurück und nimmt in Chars das Array entgegen, in die der String zeichenweise eingelesen wird.
Nun laden wir das Programm auf die SIMATIC CPU, auf unserem Linux System stellen wir sicher, dass der MQTT Service wirklich aktiv ist und loggen uns in einem weiteren Terminal als Subscriber ein. Dazu nehmen wir die Zugangsdaten, die wir im ersten Abschnitt des Beitrags konfiguriert haben. In unserem Touchpanel geben wir ebenfalls die Zugangsdaten, das Topic und die IP Adresse unseres MQTT Servers an. Nach Aktivierung durch den Switch empfangen wir in unserem Subscriber die ersten Nachrichten.
# mosquitto broker/server neu starten
sudo service mosquitto restart
# subscriber starten
mosquitto_sub -d -u simsiincosuser -P s71500siincosmqtt -t /controller1/data
Prozessdaten, Datenformat & Message Scheduling
Da die Prozessdaten maschinenbedingt vorgegeben sind, reicht in unserem Beispiel ein einziges Topic an das wir die Prozessdaten pushen. Dieses Topic wird vom Benutzer festgelegt und repräsentiert eine Maschine bzw. Anlage (oder Steuerung, je nach Sichtweise und Branche). Als Datenformat wählen wir JSON, da es sich aufgrund seiner Kompaktheit anbietet und wir letztlich ein einfaches Key-Value-Pair (Name-Wert-Paar) übertragen wollen. Die Prozessdaten, die unser Siincos Demonstrator bereitstellt sind:
- aktueller Betriebsstrom in mA
- Temperatur Sensor 1
- Temperatur Sensor 2
- Drehzahl Lüfter 1
- Drehzahl Lüfter 2
- Lüfter 1 aktiv
- Lüfter 2 aktiv
- Lüfter 1 Fehler
- Lüfter 2 Fehler
Diese wollen wir alle an unseren MQTT Server pushen. Eine Möglichkeit ist, ein Array mit der Länge "9" (0...8 vom Typ String) anzulegen und jedem Index ein Datenfeld fest zuzuweisen (siehe Screenshot des Datenblocks "mqtt_message_scheduler".mqtt_message[]
). Dies schreiben wir in die Funktion "mqtt_pub"()
, die dann bei jedem Durchlauf eine Nachricht (String) mit den aktuellen Werten pro Datenfeld generiert und im Array an seinem festen Platz ablegt ("mqtt_message_scheduler".mqtt_message[]
). Den Trigger "Clock_0.5Hz" nutzen wir auch hier, um quasi zyklisch über dieses Array zu rotieren. Dazu wird ein Counter verwendet, der bei seinem Limit "8" wieder auf "0" zurückgesetzt wird und somit eine Rotation über "9" Indizes ermöglicht. Bei jedem Trigger, wird der Counter und somit der Index des Arrays inkrementiert und der aktuelle String in den vorgesehenen Datenblock der MQTT Bibliothek kopiert. Auch hier kommt wieder die Konvertierungsfunktion "Strg_TO_Chars
" zum Einsatz, um den String in das Char Array der MQTT Bibliothek einzulesen. Parallel dazu löst der Trigger auch - wie schon oben beschrieben - den Push an den MQTT Server aus.
Nachdem das erweiterte Programm auf die Steuerung übertragen wurde, starten wir wieder - wie oben gezeigt - unseren MQTT Subscriber und aktivieren die Übertragung am HMI Panel. Anschließend sehen wir schon die neuen Nachrichten bei uns im Terminal.