Feldbussysteme sind aus der industriellen Automatisierung kaum mehr wegzudenken. Wegen gesunkener Hardwarepreise werden sie nun auch für den Einsatz zu Hause und im Büro attraktiv. Welche Möglichkeiten der ambitionierte Heimautomatisierer damit hat, beleuchtet dieser Artikel.
Feldbusbauteile galten seit jeher als hochwertige, für den Heimgebrauch jedoch zu teure Automatisierungskomponenten. Mit der immer wachsenden Zahl an Komponentenherstellern und der Verfügbarkeit freier Implementierungen von Feldbus-Stacks für Linux relativierte sich das Kostenargument in den letzten Jahren jedoch erheblich.
Acht Relais im DIN-Schienengehäuse, die der Anwender über ein standardisiertes Protokoll anspricht und die jeweils bis zu 2,5 Kilowatt schalten, sind bereits um 60 Euro zu haben [1]. Wenn man bedenkt, dass zur Ansteuerung im einfachsten Fall ein Raspberry Pi mit Netzwerkkabel ausreicht, ist dies ein konkurrenzlos günstiger Preis, den selbst proprietäre Heimautomatisierungs-Lösungen kaum unterbieten können.
Ursprünglich als Ersatz für die analoge Messwertübertragung gedacht, haben sich seit den frühen 1980er Jahren viele Feldbusprotokolle und Übertragungsstandards für verschiedene Einsatzszenarien etabliert. Auch Linux unterstützt mittlerweile einige dieser Kommunikationsprotokolle und ermöglicht damit auch das Ansprechen von Industriehardware wie beispielsweise Antrieben oder Leistungsstufen.
Im Bereich der Heimautomatisierung erfolgt typischerweise die Ansteuerung von Jalousien, Lichtern und allen Messwertaufnehmern mit Feldbussystemen. Der Vorteil gegenüber Lösungen, die auf dem I2C- oder dem MQTT-Protokoll basieren, ist, dass es wesentlich weniger zu basteln gibt. Feldbuskomponenten ersteht man üblicherweise fertig zusammengebaut und muss sie nur noch auf die DIN-Schiene setzen und mit den Aktoren oder Sensoren verbinden.
Ein Nachteil kann in der Bauteilgröße liegen. Indstriekomponenten gehen oft davon aus, dass sie in großen Schaltschränken zum Einsatz kommen, in denen insbesondere die Bauteilhöhe kaum eine Rolle spielt. Der leidgeprüfte Heimautomatisierer aber weiß, wie wertvoll der knappe Platz in den üblicherweise im Heimbereich verbauten Unterputzverteilerkästen ist.
Obwohl der Einsatz Ethernet-basierter Speziallösungen aufgrund der weiten Verbreitung des Standards und der hohen Verfügbarkeit von Netzwerkhardware auf dem Vormarsch ist [2], setzen sich Feldbustechnologien in nächster Zeit wahrscheinlich weiter durch. Eine typische Anforderung an Feldbussysteme ist die Bereitstellung eines echtzeitfähigen Kommunikationskanals mit einem vorhersehbaren Antwortzeit-Verhalten (siehe Kasten “Echtzeit”).
Echtzeit
Der Begriff Echtzeit beim Betrieb informationstechnischer Systeme meint, dass die Datenverarbeitung innerhalb einer vorbestimmten Zeitspanne abgeschlossen ist [3]. Dabei unterscheidet man zwischen harter, weicher und fester Echtzeit. Bei harter Echtzeit gilt eine Überschreitung der Antwortzeit als Versagen des Systems. Das steht im Gegensatz zur weichen Echtzeit, bei der Angaben über das Antwortzeit-Verhalten eher Richtwertcharakter haben. Feste Echtzeit ist ein Mittelding: Beim Überschreiten des Zeitrahmens droht kein unmittelbarer Schaden, die Daten sind aber nutzlos, man kann sie verwerfen.Für Einsatzszenarien, die keine echtzeitfähige Kommunikation erfordern, haben Entwickler nicht-echtzeitfähige Varianten bereits bestehender Feldbussysteme geschaffen. Ein Vertreter dieser Kategorie ist das in diesem Artikel vorgestellte Modbus/TCP. Es lässt sich auf Ethernet-Netzwerken einsetzen und zeichnet sich so durch minimale Investitionskosten aus. Alternativ gibt es Feldbussysteme, die zwar Ethernet-Hardware einsetzen, den Buszugriff jedoch durch ein anderes Buszugriffsverfahren als Ethernet regeln. In diese Kerbe schlägt Ether CAT, das das Ein-/Ausgangs-Abbild in einer festen Abfolge von Feldbusgerät zu Feldbusgerät weiterreicht, bis es wieder beim Ursprungsknoten angelangt ist. Tabelle 1 vergleicht mehrere Feldbussysteme, von denen es freie Implementierungen für Linux gibt. Später stellt der Artikel Modbus/TCP exemplarisch vor. Auch für andere Feldbus-Implementierungen gibt es Beispielprogramme frei verfügbar im Internet. Weiterführende Informationen über das Feldbussystem CAN (Controller Area Network) sind der Can4linux-Projektseite [4] oder der Linux-Kernel-Dokumentation [5] zu entnehmen.
| Modbus/TCP | Modbus/RTU über RS485 | Ether CAT | CAN | |
|---|---|---|---|---|
| Einarbeitungsaufwand | gering | gering | hoch | mittel |
| Kosten für Komponenten | gering | sehr niedrig | hoch | mittel |
| Hart echtzeitfähig | nein | nein | ja | ja |
| Maximale Buslänge | 100 m | 1200 m | 100 m (Ethernet)/20 km (Glasfaser) | 40 m (Highspeed)/500 m (Lowspeed) |
| Maximale Teilnehmerzahl je Bus | >1000 | 32 | >1000 | 110 |
| Maximale Übertragungsgeschwindigkeit | 100 MBit/s | 12 MBit/s | 100 MBit/s | 1 MBit/s (Highspeed)/125 kBit/s (Lowspeed)Einen freien Ether-CAT-Stack entwickelt die Ingenieurgemeinschaft IgH in Essen [6]. Die Technologie ist allerdings äußerst komplex und für Einsteiger auch nicht unbedingt geeignet. Für den Heimgebrauch ist Ether CAT auch zu viel des Guten. Die Komponenten sind einerseits zu teuer, andererseits übererfüllen sie die für den Heimgebrauch eher niedrigen Anforderungen an das Antwortzeit-Verhalten unnötigerweise bei Weitem. |
Diese Anforderung steht im Gegensatz zu den Grundprinzipien der Ethernet-Technologie [2]. Hier liegt bei einer geteilten Nutzung des Übertragungsmediums der Schwerpunkt auf einem möglichst hohen Datendurchsatz. Ein deterministisches Zeitverhalten in der Kommunikation war bei Ethernet kein Ziel.
Modbus/TCP-Protokoll
Dieses Protokoll gibt es bereits seit 1979. Es kommt auf seriellen (RS232, RS485) und Ethernet-basierten Medien zum Einsatz. RS485 ist ein weitverbreitetes Bussystem mit verdrillten, geschirmten Zweidrahtleitungen mit Abschlusswiderständen. Dabei sind bei geringen Übertragungsgeschwindigkeiten (115 kBaud) große Strecken von mehr als einem Kilometer überbrückbar. Mittlerweile setzen Anwender das Modbus/TCP-Protokoll auch in TCP/IP-Netzen ein, in denen die Netzwerkinfrastruktur vorhanden ist und die erforderlichen Komponenten günstig zu haben und verbreitet sind.
Modbus/TCP kennt drei Betriebsarten der Datenübertragung: RTU (Remote Terminal Unit), Ascii (American Standard Code for Information Interchange) und TCP. RTU und TCP ähneln einander und übertragen die Daten binär kodiert. Überträgt man die Daten dagegen im Ascii-Modus, können Menschen sie ohne weitere Nachbearbeitung lesen. Dieser Vorteil geht aber mit einem (noch) geringeren Datendurchsatz einher. Daher kommen fast immer die Übertragungsarten RTU und TCP zum Einsatz, nur sie erfahren Unterstützung durch Endgeräte oder Programmbibliotheken.
Die Kommunikationsarchitektur bei Modbus unterteilt Teilnehmer in Master und Slaves beziehungsweise in Clients und Server ([7], [8]). Übernimmt zum Beispiel ein Linux-PC die Rolle des Masters, darf er auf die Serverfunktionen in Ein-/Ausgabegeräten zugreifen und sie der Applikation des Anwenders zur Verfügung stellen. Modbus-Server haben bei der TCP-basierten Variante des Modbus-Protokolls die Aufgabe, per TCP (Standard: Port 502) Anfragen entgegenzunehmen und zu beantworten.
Ein Modbus-Server könnte beispielsweise ein so genannter Modbus-Koppler sein, also ein Gerät, das Nachrichten zwischen einem meist proprietären Kommunikationsprotokoll eines bestimmten Herstellers und dem Modbus-Protokoll umsetzt. Ein Client andererseits könnte ein Linux-PC sein, der ausgestattet mit der Libmodbus-Bibliothek [9] die Zustände der Eingänge ausliest und jene der Ausgänge festlegt.
An Modbus-Feldbussen sind meist Mess- und Regelsysteme angeschlossen. Für diese Anwendungsfälle und zur Darstellung der benötigten Datenstrukturen unterscheidet das Modbus-Protokoll vier Speicherarten: Holding Registers, Input Registers, Inputs und Coils [10]. Die einschlägige Literatur bezeichnet die Speicherarten auch als Objekttypen. Die digitalen Ein-/Ausgänge finden sich unter den Begriffen Inputs beziehungsweise Coils wieder. Die Begriffe Holding Registers und Input Registers beziehen sich auf Speicherbereiche, organisiert aus 16-Bit-Wörtern, die üblicherweise zur Verarbeitung von Analogwerten dienen.
Leider hat auch das Modbus-Protokoll ein paar Defizite. In der Heimautomatisierung und den dabei eingesetzten einfachen Komponenten sind diese jedoch praktisch nicht von Bedeutung. So lässt das Modbus-Protokoll bei seiner Interpretation einigen Spielraum, sodass es Unterschiede in den Implementierungen gibt. Ein Beispiel hierfür ist der Umgang mit Datenwerten höherer Auflösung (mehr als 16 Bit) in Geräten mit analogen Ein- oder Ausgängen. Allein für die Darstellung von Zahlenwerten größer als 65535 gibt es drei Vorgehensweisen: die Interpretation zweier 16-Bit-Wörter als 32-Bit-Ganzzahlenwert, die Darstellung als Gleitkommazahl nach IEEE754 oder die Darstellung im MK10-Format.
Eine weitere Herausforderung stellt die herstellerabhängige Byte-Order dar, also die Wertigkeit der einzelnen Bytes. Es empfiehlt sich daher, bereits beim Einkauf auf die Kompatibilität der vorgesehenen Modbus-Komponenten zu achten und Hinweise in den Datenblättern der Hersteller zu befolgen.
Das größte Problem von Modbus ist jedoch die IT-Sicherheit. Das Protokoll selbst verfügt über keinerlei Schutzmechanismen [11]. Diese Situation ist besonders dramatisch, da das Feldbusprotokoll üblicherweise im Bereich des Physical Computing zum Einsatz kommt. Beim Ansteuern von Aktoren, zum Beispiel von Antrieben, ist besondere Vorsicht geboten, da im schlimmsten Fall menschliches Leben gefährdet sein kann. Modbus-Komponenten sollten Anwender aus diesem Grund ausschließlich in isolierten Netzen einsetzen.
Praktischer Einsatz von Modbus/TCP
Der Testaufbau für die Beispielprogramme in diesem Artikel ist relativ simpel: Ein Linux-PC und ein Modbus-Koppler sind über ein Ethernet-Netzwerk miteinander verbunden und per TCP erreichbar. Am Modbus-Koppler wiederum ist ein Ein- und ein Ausgangsmodul angeschlossen. Zu Demonstrationszwecken verbindet der Anwender die Ein-/Ausgänge ähnlich einer Loopback-Schaltung 1:1 miteinander. Erfolgreiche Schreiboperationen lassen sich so durch das Auslesen der Eingänge verifizieren.
Server-seitig oder als Modbus-Slave spricht der Testaufbau einen Modbus-Koppler der Firma Eap Electric [12] an. Der als Basismodul B01 bezeichnete Koppler des modularen I/O-Systems namens Modul 2020 bietet die Möglichkeit, Sensoren oder wahlweise auch Aktoren zu konnektieren, und spricht diese per Modbus/RTU oder über RS485 an. Das Basismodul B02 hat genau die gleiche Aufgabe, verfügt aber über zwei Ethernet-Schnittstellen und versteht auch das Modbus/TCP-Protokoll.
Beide Basismodule warten bereits mit acht digitalen Eingängen auf, die alle mit dem in der industriellen Automatisierungstechnik typischen Spannungsniveau von 24 Volt arbeiten. An das Basismodul schließt der Anwender im vorliegenden Beispiel ein Erweiterungsmodul vom Typ E8DO-R mit acht digitalen Ausgängen an.
Der Zustand des ersten digitalen Eingangs ergibt sich dem Datenblatt zufolge aus dem Lesen von Input-Register 32. Der Wert »1« entspricht dem eingeschalteten Zustand, der Wert »0« dem Gegenteil. Den Wert des ersten digitalen Ausgangs bestimmt, was auf Coil »0« gelangt. Der Wert »0« schaltet den Ausgang aus, der Wert »1« hingegen ein.
Die Modbus-Slave-Adressen beginnen bei einem Modul der 2020-Baureihe mit »230« für die Basismodule. Die Erweiterungsmodule tragen dann aufsteigende Nummern von »231« an. Das heißt, dass die digitalen Eingänge unter der Slave-Adresse »230« und die Ausgänge unter der Slave-Adresse »231« zu finden sind. Es lassen sich bis zu 15 Erweiterungsmodule an ein Basismodul anschließen.
Clients in C++ und Python implementieren
Die folgenden Absätze demonstrieren die Implementierung dreier Clients: einer in C++, einer in Python und ein weiterer nach dem IEC-61131-3-Standard in der Programmiersprache Structured Text. Die Python-Bibliothek Pymodbus ermöglicht beispielsweise die Implementierung eines Modbus-Servers und sie erlaubt es auch, die Funktionalität des EAP-Modbus-Kopplers zu imitieren. Das ist für Testzwecke hilfreich, wenn der Anwender etwa die Funktion der zuvor erwähnten Modbus-Master testen will, ohne Hardware einsetzen zu müssen.
Für C/C++ steht die Open-Source-Bibliothek Libmodbus [9] bereit. Sie lässt sich einfach über den Paketmanager installieren, auf Debian-basierten Systemen beispielsweise mit Hilfe des Kommandos »apt-get install libmodbus-dev«. Listing 1 zeigt, dass die Implementierung eines Modbus-Masters oder -Clients als C++-Applikation in wenigen Zeilen zu bewerkstelligen ist.
Listing 1
Modbus/TCP-C++-Client
01 #include <modbus/modbus.h>
02
03 #include <iostream>
04
05 static char const* MODBUS_IP_ADDRESS = "192.168.1.123";
06 static int const MODBUS_PORT = 502;
07 static int const MODBUS_SLAVE_DI8 = 230;
08 static int const MODBUS_INPUT_DI8 = 32;
09 static int const MODBUS_SLAVE_DO8 = 231;
10 static int const MODBUS_COIL_DO8 = 0;
11
12 static uint8_t writeOutputAndVerify(modbus_t* handle, uint8_t outputBits)
13 {
14 uint8_t inputBits[1];
15 modbus_set_slave(handle, MODBUS_SLAVE_DO8);
16 modbus_write_bit(handle, MODBUS_COIL_DO8, outputBits);
17 modbus_set_slave(handle, MODBUS_SLAVE_DI8);
18 modbus_read_input_bits(handle, MODBUS_INPUT_DI8,
19 sizeof(inputBits) / sizeof(inputBits[0]), inputBits);
20 return inputBits[0];
21 }
22
23 int main(void)
24 {
25 auto handle = modbus_new_tcp(MODBUS_IP_ADDRESS, MODBUS_PORT);
26 auto rc = modbus_connect(handle);
27
28 if (rc)
29 {
30 std::cerr << modbus_strerror(rc) << "connecting to "
31 << MODBUS_IP_ADDRESS << ":" << MODBUS_PORT << std::endl;
32 }
33 else
34 {
35
36 std::cout << (writeOutputAndVerify(handle, 0x01) & 0x01) << '\n'
37 << (writeOutputAndVerify(handle, 0x00) & 0x01) << std::endl;
38 }
39
40 modbus_close(handle);
41 modbus_free(handle);
42
43 return rc;
44 }
Die Zeilen 5 bis 10 definieren die im Programm verwendeten Konstanten. In der »main«-Funktion erfolgt der Verbindungsauf- (Zeilen 25 und 26) und -abbau (Zeilen 40 und 41). In Abhängigkeit des Ergebnisses des Verbindungsaufbaus werden die Ausgänge geschrieben und die Eingänge gelesen oder eine Fehlermeldung ausgegeben (Zeilen 29 bis 38). Das Schreiben und Lesen der Aus- beziehungsweise Eingänge in der Funktion »writeOutputAndVerify()« implementieren die Zeilen 12 bis 22. Aus Gründen der Übersichtlichkeit gibt es hier keine detaillierte Fehlerbehandlung.
Die »modbus_set_slave()«-Funktion (Zeilen 15 und 17) erlaubt es bekanntzugeben, welcher Slave des gerade angesprochenen Servers zu adressieren ist. Der eigentlich schreibende Zugriff auf einen Ausgang (Coil) erfolgt dann mit Hilfe der »modebus_write_bit()«-Funktion (Zeile 16). Die Eingänge (Inputs) liest die Funktion »modbus_read_input_bits()« aus (Zeile 18).
Die Modbus-Bibliothek bietet noch weitere Funktionen mit ähnlich klingenden Namen: »modbus_read_bits()« und »modbus_write_bits()«. Während erstere Funktion dem Auslesen der Zustände der Coils dient, kommt die zweite Funktion zum Zug, wenn mehrere Coils auf einmal anzusprechen sind.
Der Aufruf des GNU C++-Compilers erzeugt ein ausführbares Binary in einem Rutsch:
g++ -Wall -Wextra -std=gnu++11 -lmodbus -o modbus-tcp-example main.cpp<C>.
Ist der Koppler erreichbar und sind Ein- und Ausgänge korrekt verbunden, so bestätigt das bei Programmausführung die Ausgabe von »0x01« und »0x00«.
Für Python steht unter anderem die umfangreiche Modbus-Bibliothek Pymodbus [13] zur Verfügung. Die Installation geschieht mit Hilfe des Pip-Paketmanagers unter Debian mit:
apt-get install python3-pip pip3 install pymodbus
Die Implementierung des Modbus-Masters in Listing 2 ist der in Python ähnlich. Nach der Konfiguration der Python-3-Logging-Mechanismen in den Zeilen 3 bis 6 erfolgt der eigentliche Einsprung ins Programm mit der »run_client()«-Funktion. Nachdem die Verbindung in den Zeilen 14 und 15 hergestellt ist, greift das Programm durch die Methoden »write_coil()« (Zeile 9) und »read_discrete_inputs()« (Zeile 10) des Client-Objekts auf den Bus zu. Das Ergebnis des Lesevorgangs landet in der Shell (Zeilen 17 und 18). Die zu erwartende Ausgabe ist ähnlich wie in Listing 1.
Listing 2
Modbus/TCP-Python-Client
01 from pymodbus.client.sync import ModbusTcpClient as ModbusClient
02
03 import logging
04 logging.basicConfig()
05 log = logging.getLogger()
06 log.setLevel(logging.INFO)
07
08 def write_output_and_verify(client, outputValue):
09 client.write_coil(0, outputValue, unit=231)
10 rr = client.read_discrete_inputs(32, count=1, unit=230)
11 return rr.bits[0]
12
13 def run_client():
14 client = ModbusClient('192.168.1.123', port=502)
15 client.connect()
16
17 log.info(write_output_and_verify(client, True))
18 log.info(write_output_and_verify(client, False))
19
20 if __name__ == "__main__":
21 run_client()
Speicherprogrammierbare Steuerungen
Für Steuerungsaufgaben nimmt man im industriellen Umfeld üblicherweise so genannte speicherprogrammierbare Steuerungen, kurz SPS,. Weil sie sich im Betrieb bewährt haben, kommen SPS-Technologien auch zusehends im Heimautomatisierungsbereich zum Einsatz. Die Programmierung dieser meist proprietären Systeme erfolgt gemäß dem IEC-61131-3-Standard. Der sieht Sprachkonstrukte vor, die das Programmieren der Steuerungslogik erleichtern.
Die Engineering-Umgebung Logi.CAD3 [14] unterstützt die Programmentwicklung mit textuellen, aber auch mit grafischen Programmiersprachen. Für Linux steht eine Betaversion im Download-Bereich der Produkt-Webseite bereit.
Listing 3 demonstriert die Entwicklung der Steuerungslogik zum Ansprechen des EAP-Kopplers mit der textuellen Programmiersprache Structured Text. In ihrer Syntax ist die Sprache stark an Pascal angelehnt. Zuweisungen führt man mit Hilfe des Operators »:=« durch. Das Gleichheitszeichen »=« dient dem Vergleich von zwei Ausdrücken und »<>« symbolisiert Ungleichheit.
Listing 3
Modbus/TCP-IEC-61131-3-Client
01 TYPE 02 APP_STATE : DINT(INVALID := 0, 03 INIT := 1, 04 WAIT_CONNECTED := 2, 05 WORK := 3, 06 FINISHED := 4); 07 END_TYPE 08 09 PROGRAM Main 10 VAR 11 app_state : APP_STATE := INIT; 12 modbus_handle : DINT := -1; 13 modbus_state : SINT; 14 bitfield : ARRAY[0..511] OF BYTE; 15 rc : DINT; 16 curEno : BOOL; 17 count_connect : DINT; 18 END_VAR 19 VAR CONSTANT 20 RTSS_LIBMODBUS_RC_OK : DINT := 0; 21 RTSS_LIBMODBUS_INVALID_HANDLE : DINT := -1; 22 RTSS_LIBMODBUS_STATE_CONNECTED : SINT := 2; 23 END_VAR 24 25 CASE app_state OF 26 APP_STATE#INIT: 27 MB_InitTCP(IPAddress := '192.168.1.123', Port := 502, 28 MBHandle => modbus_handle, ENO => curEno, RC => rc); 29 IF curEno AND rc = RTSS_LIBMODBUS_RC_OK AND 30 modbus_handle <> RTSS_LIBMODBUS_INVALID_HANDLE THEN 31 app_state := APP_STATE#WAIT_CONNECTED; 32 count_connect := 1; 33 END_IF; 34 APP_STATE#WAIT_CONNECTED: 35 modbus_state := MB_GetState(MBHandle := modbus_handle, ENO => curEno); 36 IF curEno AND modbus_state = RTSS_LIBMODBUS_STATE_CONNECTED THEN 37 app_state := APP_STATE#WORK; 38 ELSIF count_connect = 10 THEN 39 app_state := APP_STATE#FINISHED; 40 END_IF; 41 count_connect := count_connect + 1; 42 APP_STATE#WORK: 43 MB_SetSlave(MBHandle := modbus_handle, Slave := 231, ENO => curEno, RC => rc); 44 MB_WriteBit(EN := curEno, MBHandle := modbus_handle, Address := 0, 45 Value := 0, ENO => curEno, RC => rc); 46 MB_SetSlave(MBHandle := modbus_handle, Slave := 230, ENO => curEno, RC => rc); 47 MB_ReadInputBits(EN := curEno, MBHandle := modbus_handle, Data := bitfield, 48 StartInput := 32, Quantity := 1, ENO => curEno, RC => rc); 49 MB_SetSlave(MBHandle := modbus_handle, Slave := 231, ENO => curEno, RC => rc); 50 MB_WriteBit(EN := curENO, MBHandle := modbus_handle, Address := 0, Value := 1, 51 ENO => curEno, RC => rc); 52 MB_SetSlave(MBHandle := modbus_handle, Slave := 230, ENO => curEno, RC => rc); 53 MB_ReadInputBits(EN := curEno, MBHandle := modbus_handle, Data := bitfield, 54 StartInput := 32, Quantity := 1, ENO => curEno, RC => rc); 55 IF curEno AND rc = RTSS_LIBMODBUS_RC_OK AND bitfield[0] = 16#01 THEN 56 app_state := APP_STATE#FINISHED; 57 END_IF; 58 APP_STATE#FINISHED: 59 M B_Close(MBHandle := modbus_handle); 60 app_state := APP_STATE#INIT; 61 END_CASE; 62 END_PROGRAM
Funktionen können in Structured Text mehrere Rückgabewerte haben. Der Pfeiloperator »=>« weist dabei den Wert eines Rückgabeparameters an eine andere Variable zu. Ein Beispiel hierfür ist beim Aufruf der »MB_InitTCP«-Funktion ab Zeile 27 zu sehen. Die Eingangsvariable »IPAddress« bekommt den String »192.168.1.123« zugewiesen. Der Wert des Ausgangs »ENO« wird auf die lokale Variable »curEno« geschrieben. Der ENO-Ausgang ist eine weitere Spezialität der Sprachen nach der IEC-61131-3-Norm. Er stellt den Erfolg des Aufrufs dar. Ist der Ausgang »TRUE«, dann war der Aufruf erfolgreich, sonst nicht.
Charakteristisch für die SPS-Programmierung ist die zyklische Ausführung des Applikationscodes. Das Beispiel ruft den Einsprungspunkt alle 50 Millisekunden auf. Den Wert stellt die nicht angeführte SPS-Konfiguration ein. Der Einsprungspunkt ist in diesem Beispiel das Programm »Main« (Zeile 9). Die Logik des Programms ist in Form eines Zustandsautomaten implementiert.
Automaten
Die IEC 61131-3 sieht für die Umsetzung von Zustandsautomaten eine eigene Sprache namens Sequential Function Chart (SFC) vor, die aber hier wegen der großen Code-Menge, die dabei erforderlich ist, nicht zum Einsatz kommen. Stattdessen erfolgt die Implementierung über eine Variable »app_state« (Zeile 11) vom selbst definierten Aufzählungstyp »APP_STATE« (Zeilen 2 bis 6), die ein »CASE … OF«-Statement abarbeitet.
Abbildung 1 zeigt eine visuelle Darstellung des Zustandsautomaten der Applikation. Im »INIT«-Zustand (Zeilen 27 bis 33) fordert der Automat ein Modbus-Verbindungshandle zum Slave an. Ist dies erfolgreich, wechselt das Programm in den Zustand »WAIT_CONNECTED« (Zeilen 35 bis 41). In diesem Zustand verharrt es, bis die Verbindung erfolgreich aufgebaut ist. Kommt sie nicht zustande, erhöht die Variable »count_connect« ihren Wert um eins. Nach zehn Durchgängen bricht der Verbindungsaufbau ab und der Automat wechselt in den Zustand »FINISHED«. Der schließt die Verbindung ordnungsgemäß und probiert den Verbindungsaufbau erneut.
Bei erfolgreich aufgebauter Modbus-Verbindung geschehen im Zustand »WORK« (Zeilen 43 bis 54) die Schreib- und Lesezugriffe am Modbus-Slave. Ist der letzte Lesezugriff auf den Bus erfolgreich (Zeile 55), folgt ein Wechsel in den Zustand »FINISHED« (Zeilen 59 bis 60), der die Modbus-Verbindung wieder abbaut. Danach wechselt der Automat wieder in den »INIT«-Zustand zurück, um die Zustandsmaschine von vorne durchlaufen zu lassen.
Um die Clientprogramme ohne Hardware zu testen, kann der Anwender einen Modbus-Server implementieren (Listing 4). Der verhält sich wie der bisher angesprochene EAP-Modbuskoppler. Die Ausgänge sind mit den Eingängen verbunden. Diesmal geschieht dies jedoch rein in Software via Simulation, indem der Schreibzugriff auf die Coils denselben Speicherbereich betrifft wie der Lesezugriff auf die Digital Inputs.
Listing 4
Modbus/TCP-Python-Server
01 #!/usr/bin/env python3
02
03 from pymodbus.server.sync import StartTcpServer
04 from pymodbus.datastore import ModbusSparseDataBlock
05 from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
06
07 import logging
08 logging.basicConfig()
09 log = logging.getLogger()
10 log.setLevel(logging.DEBUG)
11
12 values = [False]
13
14 class CustomDataBlock(ModbusSparseDataBlock):
15
16 def __init__(self, address, values):
17 super(ModbusSparseDataBlock, self).__init__()
18 self.values = { address: values }
19
20 def setValues(self, addr, value):
21 log.debug("Will write {} to {}".format(value, addr))
22 self.values[addr][0] = value[0]
23
24 def getValues(self, addr, count=1):
25 log.debug("Will provide {} from {}".format(self.values[addr], addr))
26 return self.values[addr]
27
28
29 def run_sync_server():
30 slaves = {
31 230: ModbusSlaveContext(di = CustomDataBlock(32, values),
32 zero_mode=True),
33 231: ModbusSlaveContext(co = CustomDataBlock(0, values),
34 zero_mode=True)
35 }
36 context = ModbusServerContext(slaves=slaves, single=False)
37 StartTcpServer(context, address=("0.0.0.0", 502))
38
39 if __name__ == "__main__":
40 try:
41 run_sync_server()
42 except KeyboardInterrupt:
43 print(values)
Bei der Umsetzung wurde die Implementierung in Form eines synchronen TCP-Servers gewählt (Zeilen 3 bis 5). Das heißt, dass die Abarbeitung der TCP-Verbindungen in dem Ausführungszweig (Thread) erfolgt, der die »StartTcpServer()«-Funktion aufruft. Den Einsprungspunkt in die Applikation stellt die Bedingung in Zeile 39 und in weiterer Folge die Ausführung der Funktion »run_sync_server()« (Zeilen 30 bis 37) dar.
Die Konfiguration des Servers beziehungsweise Modbus-Slave nehmen diverse Kontexte vor. So verbergen sich hinter den Slave-IDs »230« und »231« Digital Inputs respektive Coils, die ihrerseits benutzerdefinierte Datenblöcke einstellen (Zeilen 30 bis 34).
Die benutzerdefinierten Datenblöcke vom Typ »CustomDataBlock« (Zeilen 14 bis 26) erlauben das Lesen und Schreiben in den gemeinsamen Speicherbereich »values« (Zeile 12) durch Überschreiben der Callback-Methoden »setValues()« (Zeilen 20 bis 22) und »getValues()« (Zeilen 24 bis 26). Der Konstruktor (Zeilen 16 bis 18) initialisiert das »values«-Property für den Unterbau der Pymodbus-Bibliothek. Dieses Property muss existieren, da vor dem eigentlichen Aufruf der Callbacks intern Validierungen laufen, die das Property benötigen.
Bei der Zuweisung an die »ModbusSlaveContext«-Konstruktoren ist zusätzlich zu den »CustomDataBlock«-Objekten der Parameter »zero_mode« auf den Wert »True« zu setzen, da sonst zu den Slave-Adressen der Master-Anfragen 1 hinzugezählt wird und der Master die angeführten Slaves dann nicht mehr findet. Die Zuweisung des Werts »False« an den Parameter »single« (Zeile 36) ist erforderlich, damit der Modbus-Server zwischen unterschiedlichen Slave-Adressen unterscheiden kann.
Der Modbus/TCP-Slave aus Listing 4 lässt sich nun im Zusammenspiel mit den Modbus/TCP-Mastern aus den Listings 1 bis 3 einsetzen. Damit eine erfolgreiche Kommunikation möglich ist, sind natürlich die IP-Adressen und Ports im Quellcode an die eigene Umgebung anzupassen. Das ist einfach, da die Werte in Konstanten gespeichert sind. Stellt der Anwender die verwendeten TCP-Ports auf Werte größer 1023 ein, sind für die Ausführung der Beispiele keine Rootrechte erforderlich.
Fazit
Dieser Artikel zeigt, dass Linux Feldbustechnologien in der Praxis unterstützt und diese sich für Automatisierungsaufgaben einsetzen lassen. Viele Bibliotheken für Feldbusprotokolle sind in C oder C++ umgesetzt, sodass dank geeignetem Glue-Code auch ein Einsatz in diversen Skriptsprachen denkbar ist.
Es ist damit zu rechnen, dass etablierte Feldbustechnologien nicht von einem Tag auf den anderen verschwinden. Es kommen aber bis hin zur Thing-Ebene, also der Geräte-Ebene, sicher noch umfangreichere, in weiten Teilen standardisierte Protokolle wie zum Beispiel OPC Unified Architecture [15] hinzu und lassen die vierte industrielle Revolution Realität werden [16].
Auch Alleinstellungsmerkmale von Feldbustechnologien, zum Beispiel die Echtzeitfähigkeit, finden bei der Weiterentwicklung der Standards für Ethernet-basierte Netzwerke Berücksichtigung. Time-sensitive Networks (TSN) erlauben es mit Hilfe vorbestimmter Kommunikationskanäle, das Antwortzeit-Verhalten deterministisch zu gestalten [17]. Ursprünglich für die Übertragung von ebenfalls zeitkritischen Audio-/Videodaten vorgesehen, gibt es für Linux bereits Referenzimplementierungen [18]. Die befinden sich allerdings erst im Betastadium und sind daher noch nicht praxistauglich.
Infos
- KMtronic, Modbus-Produkte: https://sigma-shop.com/category/29/modbus.html
- Ethernet vs. fieldbus, “The right network for the right application”: https://www.controldesign.com/articles/2016/ethernet-vs-fieldbus-the-right-network-for-the-right-application
- Echtzeitsystem (Wikipedia): https://de.wikipedia.org/wiki/Echtzeitsystem
- Can4linux-Programmbeispiele: http://svn.code.sf.net/p/can4linux/code/trunk/can4linux-examples
- Linux Networking Documentation – SocketCAN – Controller Area Network: https://www.kernel.org/doc/html/latest/networking/can.html
- IgH Ether-CAT-Master für Linux: http://etherlab.org/en/ethercat
- Modbus-Protokoll: http://www.interlog.com/~speff/usefulinfo/modbus_protocol.pdf
- “Modbus Messaging on TCP/IP Implementation Guide V1.0b”: http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf
- “Libmodbus – A Modbus library for Linux, Mac OS X, FreeBSD, QNX and Win32”: http://libmodbus.org
- Modbus for Field Technicians: http://www.modbusbacnet.com/includes/pdf/MODBUS_2010Nov12.pdf
- Modbus Security: https://www.rtaautomation.com/blog/modbus-security
- EAP Electric Modbus Modul 2020: http://www.eap-electric.at/de/produkte/automationstechnik-buskomponenten/modul-2020/product/modul-2020
- Pymodbus: https://github.com/riptideio/pymodbus
- Logi.CAD3: https://www.logicals.com/de/logi-cad/die-engineering-plattform
- OPC Unified Architecture: https://opcfoundation.org/about/opc-technologies/opc-ua
- Von Industrie 1.0 bis 4.0 – Industrie im Wandel der Zeit: http://industrie-wegweiser.de/von-industrie-1-0-bis-4-0-industrie-im-wandel-der-zeit
- What is Time-sensitive networking? https://www.controldesign.com/articles/2016/what-is-time-sensitive-networking-iiot
- TSN driver for the kernel: https://lwn.net/Articles/690998








gcc und g++ benötigen das main.cpp/.c am anfang nicht am ende:
FALSCH: g++ -Wall -Wextra -std=gnu++11 -lmodbus -o modbus-tcp-example main.cpp
-> Liefert einige: undefined reference to ‘..’
RICHTIG: g++ main.cpp -Wall -Wextra -std=gnu++11 -lmodbus -o modbus-tcp-example
Hat mich grad 5 Stunden Lebenszeit und Nerven gekostet.
Hallo Herr Tschopp, vielen dank für ihren hilfreichen Kommentar. Es tut mir leid, dass sie durch die inkorrekte Anordnung der Argumente des GCC Compiler-/Linker-Aufrufs im Artikel Zeit verloren haben. Vielleicht entschädigen meine Erklärungen ein wenig für ihren Ärger. Bei der Erstellung des Artikels hatte ich den “Build” der Beispiele mit Hilfe von CMake durchgeführt. Dieses kümmert sich selbstständig darum, dass die Abhängigkeiten bei Compiler-/Linker-Aufrufen korrekt angeordnet werden. Der Grund, warum die Source-Datei (main.cpp) an erster Stelle stehen muss, ergründet sich in der Art, wie Linker den Zusammenhang der sogenannten Symbole (vereinfacht: globale Funktionen und Variablen) auflösen. Die Verwendung von Symbolen… Mehr »