Kategorien
Steuerungen

Modbus-Kommunikation mit RaspberryPi und PyModbus

Wie man vielleicht bemerkt, beschäftige ich mich gerade mal wieder mit einem meiner Lieblingsgeräte, dem RaspberryPi, lange lagen die beiden Modelle hier herum und staubten ein, in der letzten Zeit müssen sie wieder viel über sich ergehen lassen.

Auf wemaflo.net schrieb ich schon einmal über den RaspberryPi und Modbus, der Artikel bietet aber nicht wirklich viel Mehrwert, daher habe ich mich entschlossen, ihn neu zu schreiben, statt ihn zu übernehmen. Los geht’s!

Ich habe also:

  • Einen RaspberryPi B+ V1.2 mit CODESYS-Laufzeit, welchen ich als Controller benutze (Installation ist hier beschrieben)
  • Einen RaspberryPi B V1, welchen ich als Modbus-Master nutze

Auf beiden Geräten läuft Debian bzw. Raspbian.

RaspberryPi B und RaspberryPi B+
RaspberryPi B und RaspberryPi B+

Zuerst installieren wir auf dem Modbus-Master den Python-Paketinstaller easy_install:

wget https://bootstrap.pypa.io/ez_setup.py -O - | python

Danach installieren wir noch build-essential (vermutlich schon vorhanden) und das Dev-Paket von Python 2.7:

apt-get install build-essential python2.7-dev

Nun fehlt noch das tatsächliche Modbus-Programm, welches wir nun über easy_install installieren (was einen Moment dauern kann):

easy_install -U pymodbus

Nun fehlt noch ein Python-Programm, welches die PyModbus-Bibliothek nutzt. Ich habe hier ein einfaches Beispielprogramm geschrieben, welches einen Inputwert abfragt und diesen dann so lange per Modbus auf den Slave schreibt, bis es mit Enter abgebrochen wird (die IP-Adresse des Modbus-Slave muss natürlich angepasst werden):

#!/usr/bin/env python
from pymodbus.client.sync import ModbusTcpClient as ModbusClient
import thread, time

def input_thread(L):
        raw_input()
        L.append(None)

def do_write_modbus():
        L = []
        thread.start_new_thread(input_thread, (L,))

        #Modbus-Slave
        RPiCODESYS = ModbusClient(host = '192.168.178.123', port=502)
        write_value = input ('Inputvalue: ')
        loop = input ('Loop starten: ')
        write_register = 0

        while 1:
                time.sleep(.1)
                if L: break
                RPiCODESYS.write_register(write_register, write_value)

do_write_modbus()

Herunterladen: Python-Script: Modbustest

Dieses Programm kann nun mit python ausgeführt werden (python modbustest.py).

Ohne Modbus-Slave auf der Gegenseite hilft das aber wenig, wir benötigen noch einen Beweis, dass alles funktioniert.
Unter CODESYS 3 sollten wir nun also ein neues Projekt erstellen, in meinem Falle wieder mit dem RaspberryPi als Zielsystem. Per Rechtsklick auf das Device kann nun ein weiteres Gerät angehängt werden, hier wählen wir den Ethernet-Adapter.

Gerät an den RaspberryPi anhängen
Gerät an den RaspberryPi anhängen

Per Rechtsklick auf den neuen Ethernet-Adapter hängen wir nun noch das Modbus TCP Slave Device an.

Modbus Slave Device an den Ethernet-Adapter anhängen
Modbus Slave Device an den Ethernet-Adapter anhängen

Nun schreiben wir eine Variable in unser Standard-Programm PLC_PRG, um an dieser nachher die über Modbus geschriebenen Werte sehen zu können:

Variable wModbusInput in PLC_PRG einfügen
Variable wModbusInput in PLC_PRG einfügen

Auf diese Variable können wir nun unseren Modbus-Input mappen. In das entsprechende Menü gelangen wir durch einen Doppelklick auf den Modbus TCP Slave:

Modbus-Input auf wModbusInput mappen
Modbus-Input auf wModbusInput mappen

Nun muss nur noch die Verbindung mit dem Controller hergestellt werden. Hierzu öffnen wir per Doppelklick auf den RaspberryPi (Device CODESYS Control for Raspberry Pi) das entsprechende Menü und geben dort die IP des Gerätes ein. Mit Enter wird die IP übernommen und die Verbindung überprüft.

CODESYS mit RaspberryPi verbinden
CODESYS mit RaspberryPi verbinden

Per Klick auf das Zahnrad (oder Alt + F8) loggen wir uns auf dem Gerät ein und starten das Programm mit F5. Nun können wir im PLC_PRG in Quasi-Echtzeit unsere Eingaben aus modbustest.py sehen.

modbustest.py schreibt Daten auf CODESYS-Slave
modbustest.py schreibt Daten auf CODESYS-Slave

Jetzt bin ich gespannt: Funktioniert bei euch auch alles (wenn nein helfe ich gerne) und wofür benutzt ihr das ganze?

40 Antworten auf „Modbus-Kommunikation mit RaspberryPi und PyModbus“

Hi, super Anleitung,
bräuchte aber deine Hilfe.

Habe nun auch 2 Pi´s einen mit Codesys (Slave) einen mit Debian und dem Pythonscript.

das Script läuft und auf dem anderen läuft Codesys.

wie hast den du die Pi´s und PC verbunden so dass du noch über PC eine Verbindung zum Pi mit Codesys hast um hier Werte zu ändern?

Das verstehe ich nicht ganz. Du gibts jedem pi eine statische IP, schließt sie an einen Switch an, mit dem du auch mit dem PC verbunden bist. Dann müsstest du doch ganz normal auf die Geräte zugreifen können, oder verstehe ich was falsch?

hy,

du hast nicht auch zufällig Tipps für Modbus RTU?
Wäre sehr hilfreich ein bisschen Unterstützung zu erhalten, weil ich habe bisher weder mit Python noch mit Modbus gearbeitet.
Danke schon mal.

Hy Florian,
Danke für die schnelle Antwort. Ich hätte jetzt mit dem Quellcode ‚Minimalmodebus‘ gearbeitet, weil der nicht so umfangreich ist. Eigentlich wollte ich mit ‚pymodbus‘ arbeiten aber ich brauch eine schnelle Lösung, die funktioniert.
Danke für deinen Tipp

Hallo!
Danke für die Anleitung; hatte folgendes Problem:
Bibliothek ist wie beschrieben hinzugefügt, beim Zahnrad gibts aber folgende Meldungen „Bibliothek ModbusTCP Slave, 3.5.5.0 (…) wurde nicht zum Bibliotheksverwalter hinzugefügt, oder es wurde keine gültige Lizenz gefunden“, gefolgt von „Unbekannter Typ: ‚ModbusServer'“ etc.. Erst ein „Download fehlender Bibliotheken“ hat weitergeholfen (Modbus TCP Slave kam dazu).

Gebe ich nun ein paar Zeichen ein, passiert nichts. Drücke ich in putty die Enter-Taste, kommt „Loop starten“ – bei Eingabe von 1 beendet sich das Programm.
Ist mein erstes Codesys-Projekt.

Vielen Dank!
Andreas

Hallo,

erstmal vielen Dank für die Anleitung, super gemacht!
Ist es auch möglich, dass man unter CODESYS einen Modbus-TCP-Master einhängt und dann mehrere Raspberry Pis mit der pymodbus-Lib als Slaves betreibt? Gibt es hier auch so ein schönes Beispiel? Ich bin leider ein Python Noob und muss im ersten Schritt einen (fast)fertigen Code verwenden.

Vielen Dank im Voraus!
Tobi

Hallo Florian,

Tobis Frage ist genau das, was ich auch suche. Irgend etwas einfaches, was erstmal geht und dann aufgeblasen werden kann. Ich komme aus der SPS-Welt und habe leider nicht die Zeit, mich in die Tiefen von Python und linux einzuarbeiten. Ein Raspberry mit CoDeSys liegt schon hier und läuft. Meine Idee ist es, die dezentrale Peripherie ebenfalls in den Raspberrys laufen zu lassen und das dann bei Verfügbarkeit auf die Zeros zu packen, also bei Standard-I/Os mit immer gleichem Programm, bei Sonderklamotten (Servos, schnelle Zähler, Schrittmotoren…) mit extra Programm.

Besten Dank

Hallo,

ich hoffe ich finde demnächst mal Zeit und Lust noch einmal ein etwas umfassenderes Tutorial zu schreiben und dabei auf die Kommentare hier einzugehen. Leider ist tatsächlich vieles von Pymodbus etwas kompliziert dokumentiert, daher muss ich mir dafür auch etwas Zeit nehmen und herumprobieren.

Hmm…sehr Interessant, ich bin schon gespannt wie das bei mir zuhause dann funktioniert. Ich möchte eine Wärmepumpe damit ansprechen, so das ich alle Daten aus dem Gerät abgreifen und an eine Datenbank mit Grafik davor geschaltet weitergeben kann, im Rahmen eines HomeAutomation Projektes.

Hallo,

vielen Dank für diese Webseite mit den vielen tollen Projekten.
Ich bastle zurzeit auch bissle mit dem Raspberry (SPS-Codesys) an einer Hausautomation rum. Zurzeit versuche ich zwei Pi’s (B und B+) über Modbus TCP zu verbinden, bekomme den Slave (Pi Modell B) aber nicht zum laufen, es wird ein Busfehler angezeigt? Kann mir jemand helfen?

Hallo Hendrik,
das mit dem Busfehler hatte ich erst auch aber dann hat es Funktioniert. Wichtig ist die richtige Konfiguration des Ethernet Geräts und des ModbusTCP_Slave_Device in Codesys.
Beim Slave_Device muss die IP Adresse des anderen RPi mit dem Pythonskript eingetragen sein. Dann sollte es Funktionieren.

@Florian:
Danke für das tolle Anleitung.

Da ich mich mit Python nicht so auskenne würde mich ein Pythonskript mit mehreren Werten / Variablen z.Bsp von den DS18B20 Sensoren die Zyklisch übertragen werden sehr interessieren. Oder auch Werte von Codesys empfangen…
Hast du sowas schon Programmiert bzw. ein Bespiel hierfür?

Hallo,
zunächst einmal vielen Dank für das Tutorial. Stehe grade vor folgendem Problem:
-codesys 3.5 ist drauf
-Programm (fup) läuft
-ein Pi 3 vorhanden und die Webvisu läuft auch bereits.
Habe jetzt die 750-628 hier und unter anderem 2 I/O karten 1405 & 1504.
-pi & 628 mit der fritzbox verbunden
Meine Frage:
1. bei der 628 leuchtet permanent nur die Rote LED (also STOP, normal ist das nicht oder)
und 2. Wie bekomme ich die I/O Karten damit verbunden.

Am 23.12 ist Einzug im Haus und meine Freundin würde sich freuen wenn wir Licht hätten =D

Hallo Daniel,

die Klemme 750-628 ist eine Klemmenbus-Verlängerung. Um die zu betreiben, benötigst du einen Controller der 750-Serie, mindestens eine I/O-Klemme und danach eine Klemmenbus-Verlängerungs-Endklemme.

Diese Klemme kannst du nicht einfach an irgendein Netzwerk anschließen, auch wenn sie RJ45-Buchsen hat, die Beschaltung ist anders und es läuft auch kein Netzwerk-Protokoll, sondern der K-Bus.

Sprich: Du benötigst zumindest einen Modbusfähigen Feldbuskoppler, um die Karten über Modbus anzusteuern.

Liebe Grüße!

Ok, sowas ähnlich habe ich mir ehrlich gesagt auch schon fast gedacht. Jedoch gab es böse Zungen die gesagt haben das würde gehen.

Zur Verfügung habe ich noch eine wago 750-841 (die geht ja laut wago nur mit codesys 2.x) deswegen habe ich diese erstmal außen vorgelassen. Momentan habe ich > 628 > 1405 >1504 > 600. Also mit Abschlussklemme.

Also wenn ich statt der 628 die 750-841 ran mache sollte das ja funktionieren… hoffentlich.
Dann ist die Frage wie bekomme ich das mit codesys 3.5 ins laufen und wie mach ich das Pi ran. Die sps einfach aufbauen und dann sps & pi in hub /switch rein ?? Ein Wago Kabel zum Programmieren habe ich nicht und mein Pi hat auch kein W-Lan dran … heißt wenn ich die zwei verbinden würde … könnte ich die nicht mehr programmieren (es sei denn ich verbinde alles mit meine Fritz box). Eine 750-653 hätte ich auch noch zur Verfügung falls mir die weiter helfen würde

Ps. Danke für die schnelle Antwort

Der Controller 750-841 geht tatsächlich nur mit CoDeSys 2.3, allerdings kannst du den Controller ja auch als Modbuskoppler einsetzen, wenn du gerne den Raspi mit CODESYS 3.5 nutzen möchtest. Du kannst ja auch einen alten Controller von einem CODESYS 3.5 Gerät ansprechen, Modbus ist ja ein Standard, der überall gleich ist.

Wenn du dem Raspi und dem Controller jeweils eine IP im gleichen Netz wie die FritzBox gibst, sollte es kein Problem sein, beide daran anzuschließen, dann kannst du im LAN auch beide Geräte bequem erreichen und programmieren.

Ein Programmierkabel könntest du dir übrigens für sehr kleines Geld selber basteln:
https://florianmai.de/2016/04/07/service-kabel-z-b-fuer-wago-controller-sehr-guenstig-selbst-gemacht/

Vielen Dank für den Tipp. Ab und zu brauch man mal frischen Input. In der Uni passt ja immer alles auf Anhieb… kaum ist man raus weiß man nix mehr… Werde wenn Interesse besteht sobald es klappt Feedback geben.

Vielen Dank für die Hilfe

Hallo Florian,
habe jetzt dem 841 eine IP verpasst und er erkennt auch die Karten (zumindest im Web-based Management). Habe das PI ja schon laufen gehabt. Habe mit dem Video von Kurt Braun auch alles soweit eingestellt, nur habe ich ein FUP schon stehen.
Wenn ich den Variablen keine Adresse angebe (%IX0.0) kommt vor den Ethernet Symbolen kein Fehler und jeder Status ist Betriebsbereit (Master Slave). Sobald ich diese zuweiße steht beim Slave (841) ein Dreieck mit dem hinweiß (Der Bus Läuft nicht). Die Visu läuft aber bekomme diese I/O nicht ins laufen…

Hast du irgend eine Idee? Danke in Vorraus

Hi Daniel,

um welches Video geht es denn?

Wenn ich das richtig verstehe, hast du nun:
Einen Raspberry Pi mit CODESYS-Runtime und einem Programm in FUP, das per Modbus die Outputs des 841 schreiben soll, bzw. die Inputs lesen. Korrekt?

Okay, ich habe diese Konstellation noch nicht ausprobiert, vielleicht komme ich morgen mal dazu.
Nur als Hinweis: 0-15 sind 16 Bit, bzw. 1 Word 😉 Die Null zählt auch.

Hast du im Web Based Management Modbus aktiviert (Portkonfiguration)? Ich weiß nicht, ob das bei den alten Controllern standardmäßig aktiv ist.
Stimmt das Offset für den Zugriff auf die Outputs? Ich weiß nicht, ob es auch beim 841 bei 512 liegt…

Das die bei 0 anfangen war mir soweit ja klar. Im Web Management ist soweit alles freigegeben. Der Offset sollte laut Handbuch auch bei der 841 für Ausgänge bei 512 anfangen …

Hi Florian,
auch von mir vielen Dank für Dein Tutorial.
Gibt es vielleicht schon Neuigkeiten für die Frage von Tobi aus dem April?
Also einen Pi als Master auf dem codesys mit Visu läuft und mehrere Slaves, die Daten bekommen oder liefern…
Viele Grüße!!

Hi. Auf meinem PFC200 von Wago bekomme ich nach deiner Anleitung tatsächlich den Wert übertragen.

Ich möchte den Raspberry als „Fernbedienung für meine Schranke benutzen.

Also 4 Tasten am Raspi, die über verschienen Variablen Funktionen am Wago PFC ausführen und wenn offen oder zu oder Lichtschranke usw. sollen die Tasten am Raspi leuchten(Relaismodul am GPIO)

Vielleicht gibts da ja doch schon ein Super Beispielprojekt…

Ich habe nämlich echt Probleme, das WORD in BOOL’s umzuwandeln und BOOL’s in WORD…

Das am Raspi werd ich schon irgendwie hinbekommen, dass die GPIO’s angesteuert oder ausgelesen werden… Denke ich…

mfg

d.langhojer

Hallo Florian,

leider funktioniert die Kommunikation bei mir (noch) nicht. Ich habe im CoDeSys unter Ethernet die Netzwerkschnittstelle ausgewählt, die der Raspberry mit der CoDeSys Runtime (Also der Slave) mit dem Netzwerk verbunden ist. ANsonsten habe ich hier keine Einstellungen vorgenommen.

Beim Slave Device habe ich nur den Eingang wie oben gemappt.

Nun starte ich auf dem zweiten Raspberry dein modbustest-Script, gebe als Inputvalue 255 ein und drücke Enter. Nun erscheint im putty eine Leerzeile in der der Cursor steht.

Im CoDeSys bleibt der Wert der angelegten Variable jedoch auf 0. Was mache ich falsch?

Gruß,

Bastian

da ne 1 rein und enter. beim nächsten enter beendet er das dann.

Ich werd das dann nochmal ausprobieren und mich nochmal melden.

genau. klappt so. nur! nicht vergessen den raspi vorher mit „sudo -s“ auf root umzustellen.

Hi. ich weiß nicht, ob da noch jemand mitliest… ABER:

wie kann ich da modbus lesen?

schreiben klappt, nur muss ich noch schauen, ob ich den thread brauche… Hier mal das Taster zu Modbus Progrämmchen, das noch ein paar Problemchen hat. Das ganze wird ganz „easy-going“ per FUP am Wago Controller in vier Boolsche Variablen wieder aufgeteilt…

Würde mich freuen, wenn jemand mal was dazu zu sagen hat…

#Timer Setting
from time import sleep
from pymodbus.client.sync import ModbusTcpClient as ModbusClient
import thread, time

#GPIO Settings
from RPi import GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)

#GPIO.input(18) Schranke 1
GPIO.setup(18, GPIO.IN)
#GPIO.input(23) Schranke 2
GPIO.setup(23, GPIO.IN)
#GPIO.input(24) Sirene tasten
GPIO.setup(24, GPIO.IN)
#Relais4 Brauchwasserpumpe laeuft Ueberwachung
GPIO.setup(25, GPIO.IN)

MB_1=0000

while True:
if GPIO.input(18) and not GPIO.input(23) and not GPIO.input(24) and not GPIO.input(25):
MB_1 = 0001
elif not GPIO.input(18) and GPIO.input(23) and not GPIO.input(24) and not GPIO.input(25):
MB_1 = 0010
elif GPIO.input(18) and GPIO.input(23) and not GPIO.input(24) and not GPIO.input(25):
MB_1 = 0011
elif not GPIO.input(18) and not GPIO.input(23) and GPIO.input(24) and not GPIO.input(25):
MB_1 = 0100
elif GPIO.input(18) and not GPIO.input(23) and GPIO.input(24) and not GPIO.input(25):
MB_1 = 0101
elif not GPIO.input(18) and GPIO.input(23) and GPIO.input(24) and not GPIO.input(25):
MB_1 = 0110
elif GPIO.input(18) and GPIO.input(23) and GPIO.input(24) and not GPIO.input(25):
MB_1 = 0111
elif not GPIO.input(18) and not GPIO.input(23) and not GPIO.input(24) and GPIO.input(25):
MB_1 = 1000
elif GPIO.input(18) and not GPIO.input(23) and not GPIO.input(24) and GPIO.input(25):
MB_1 = 1001
elif not GPIO.input(18) and GPIO.input(23) and not GPIO.input(24) and GPIO.input(25):
MB_1 = 1010
elif GPIO.input(18) and GPIO.input(23) and not GPIO.input(24) and GPIO.input(25):
MB_1 = 1011
elif not GPIO.input(18) and not GPIO.input(23) and GPIO.input(24) and GPIO.input(25):
MB_1 = 1100
elif GPIO.input(18) and not GPIO.input(23) and GPIO.input(24) and GPIO.input(25):
MB_1 = 1101
elif not GPIO.input(18) and GPIO.input(23) and GPIO.input(24) and GPIO.input(25):
MB_1 = 1110
elif GPIO.input(18) and GPIO.input(23) and GPIO.input(24) and GPIO.input(25):
MB_1 = 1111
else:
MB_1 = 0000

#Modbus-Slave
RPiCODESYS = ModbusClient(host = ‚192.168.178.70‘, port=502)
write_register = 0
RPiCODESYS.write_register(write_register, MB_1)

#Delay
sleep(1)

Hallo Daniel,

natürlich lese ich hier immer mit, habe aber leider gerade nicht so recht Zeit, mich mit dem Thema noch mal auseinander zu setzen. Sollte ich mal wieder einen RasPi raus kramen, sehe ich mir das aber mal an.

PS: Da ich die Eingänge jetzt mit:

GPIO.setup(18, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

definiert habe, kann ich tatsächlich die Variablen tasten.

ABER!!! Leider überträgt mir das ZEUGS nur 0000 oder nur wenn alle vier Taster gedrückt werden 1111…

Was hab ich da in der If Schleife falsch gemacht???

HILFE! Biddööö

Ohne jetzt lange drüber gesehen zu haben, sieht deine Schleife logisch richtig aus. Allerdings gibt es, zumindest hier im Kommentar, keine Einrückung, die aber bei Python anstelle von Klammern genutzt wird, um Code zu verschachteln. Ich bin mir nicht sicher, wie Python damit umgeht, wenn man das weg lässt.

Hi. Ich hab das inzwischen mit einzelnen =GPIO.HIGH und =GPIO.LOW vergleichen getauscht.

Jetzt bekomm ich an der Wago SPS auch die Werte (als WORD) wie ich sie vom Raspi aus schreibe.

(wieder) ABER:

Ich vergleiche die kommenden WORD-Werde mit vorher als WORD gesetzten Variablen, in die ich
(PS. Ich habe eine 5 voran gesetzt. Also 50000 bis 51111, damit auch am Raspi WORD ist)
diese 50000 bis 51111 eingesetzt habe, in FUP per EQ-Baustein.

Sieht echt supi aus, dass er 50001 mit 50001 vergleicht und trotzdem nicht auf TRUE schaltet. Wieder erst wenn 51111 kommt kommen alle 4 Tastervariablen an der Wago-SPS auf True…

???? Ich versteh gar nix mehr.

Ich denke aber, dass ich erst diese Richtung (vom Raspi zur Wago) perfekt haben muss, damit ich mich dann der Lese-Richtung widmen kann.

PS…: Hast du dich damit schon beschäftigt?

Lg
Daniel

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert