okay,
also der Feinstaubsensor arbetie nicht mit Modbus, sondern über serielle Kommandos mit einer "normalen" seriellen Schnittstelle. Die steuerst Du über Pyhton an oder wie? Und der Anschluss geht über USB? Dann "USBtty0" als Schnittstelle? Oder kommunizierst Du mit dem Sensor auch über Node-Red?
Da ich das Gefühl habe, dass Du irgendwie sehr komplexe Umwege gehst, beschreibe ich Dir hier mal, wie ich die Sache gelöst hätte. Bei der USB-Kommunikation gehe ich davon aus, dass sie über einen normale TTY (serielle) Schnittstelle realisiert ist und dass einzelen Kommandos als Strings gesendet und eingelesen werden....
Ich gehe auch davon aus, dass Du mit sudo apt-get update und dann mit sudo apt-egt Upgrade die Pakete auf den aktuellen fehlerfreien Stand gebracht hast.
Außerdem gehe ich von einer intakten Netzwerkanbindung aus, bei der der RevPi eine IP Adresse von z.B. 192.168.1.21 hat. Die Modbus Dienste "Master" und "Slave" sind im Web-Status vom RevPi über einen angeschlossenen Browser unter der Registerkarte "Services" auf "Enabled" gestellt:
- 001.PNG (139.58 KiB) Viewed 11142 times
Also: Zunächst wird in PiCtory ein Modbus TCP Slave aufgesetzt. Dadurch stehen 32 Register für Ein- und 32 für Ausgaben bereit. Diese heißen "Input_1" bis "Input_32" und "Output_1" bis "Output_32". Die "Inputsd" entsprechen unter Modbus den Ausgangs-Registern, denn in Modbus ist die Bezeichnung immer aus Sicht des Masters (der Steuerung): Der Master (die Steuerung) schreibt in die Output Register oder Coils und liest aus den Input-Registern oder "Contacts". Ausnahme sind die holding-Register, die eigentlich outputs sind, aber auch wieder eingelesen werden können. Modbus stell für jeden dieser Registertypen eigene Befehle zum Lesen und Schreiben bereit. Unser Slave kann aber nur die Befehle "read input registers" (Befehlscode hex04), "read holding registers" (Befehlscode hex03) sowie "write single register" (Befehlscode hex06) und "write multiple registers (Befehlscode hex10) beantworten und verarbeiten. Dabei werden id mit "Inputxx" benannten Speicherzellen im Prozessabbild ("PA") vom RevPi für die "write" Befehle sowie den "read holding registers" Befehl bereitgestellt und die mit "Outputxx" bezeichneten Adressen im PA werden für den "read input registers" Befehl verwendet. "Input_01" enthält dabei die Werte, die in das Modbus-Register 01 geschrieben wurden und "Ouput_01" enthält die Werte, die mit "read input registers" aus der Modbus-Adresse 01 gelesen werden.
Im Valueeditor rechts unten in PiCtory belassen wir den TCP-Port auf seiner Defaulteinstellung 502, was dem üblichen Port für Modbus TCP entspricht. Die "max._modbus_TCP_connections" sind mit 10 auch okay, wir brauchen ja eigentlich nur eine Verbindung. Um nun in Python einfacher zugreifen zu können, lassen wir uns die Speicher-Adress-Offsets der PA-Werte "Input_01" bis "Input_32" sowie "Output_01" bis "Output_32" anzeigen. Dafür klicken wir bei allen diesen Wertzen im Value-Editor das Kästchen "Export" an (geht aktuell leider nur mit 64 einzelnen klicks
). Dann kannst Du über "File "Save" und dann "File" "Export" mit der Option "Show only" und "Offset Liste", dann "OK" eine Liste am Bildschirm anzeigen lassen:
- 02.PNG (16.26 KiB) Viewed 11142 times
- 03.PNG (18.21 KiB) Viewed 11142 times
Wie man dort sieht, haben die Input_xx Datenworte (Modbusregister) einen Offset von 11 bis 73. Man kann ihn auch ausrechnen mit der Formel: Offset = 9 + 2 * xx.
Die Ouput_XX Datenworte (Modbusregister) haben einen Offset von 75 bis 137. Man kann ihn auch ausrechnen mit der Formel: Offset = 73 + 2 * xx.
Außerdem sieht man in der Exportliste auch Werte des RevPi Core 3, die im PA abrufbar sind: Neben Status und LED sind (wenn man den Haken Export beim Core entsprechend setzt) auch die CPU-Temperatur und Taktfrequenz als einzelne Bytes auf Offset 4 (CPU Temperatur in °C) und 5 (Frequenz in 10 MHz) abrufbar.
Wenn Du jetzt auf Komanndozeile von Linux das Kommando piTest -x aufrust, dann wird PiControl einschließlich des Modbus Slaves neu gestartet und Du kannst zum Beispielk mit piTest -r 4,1 die CPU Temperatur im Prozessorkern ablesen. Alternativ ginge das natürlich beim Kommando piTest auch mit dem symbolischen Namen: piTest -r Core_Temperatur.
Wenn Du in eines der Output_XX eine Wert hineinschreibst, dann kannst Du diesen Wert von Deinem Modbus Master aus abfragen:
Mach das einfach mal von der Kommandozeile aus: piTest -w Output_1, 12345, Das system antwortet mit "Write value 12345 dez (=3039 hex) to offset 75." Alternativ hättest Du auch so schreiben können: piTest -w 75,2,12345
Jetzt starte einen Modbus master und sende an slave mit der IP 192.168.0.21 auf Port 502 einen Modbusbefehl "Read Input Registers" auf das einzelne Register 1 aus. Der Master sollte melden, dass der slave mit 12345 als Registerinhalt geantwortet hat. Hie rmal als Beispiel so etwas mit dem Windowsprogramm Qmodmaster durchgeführt:
- 04.PNG (23.82 KiB) Viewed 11142 times
Hier siehst Du auch gleich eine wichtige Besonderheit von Modbus: Leider ist die Zählung der Register nicht so einheitlich, wie das wünschenswert wäre. Wir bei KUNBUS haben in unserem Modbus master und slave die Registernummern verwendet, die bei 1 anfangen. In den Modbustelegrammen werden aber die eigentlichen Modbsuadressen versendet, die bei 0 anfangen. Um also das register 1 zu lesen versendet der Master ein telegramm mit der Abfrage der Registeradresse 1 beim Slave. Darum steht hier unter "Start Address" beim QmodMaster eine "0".
Du könnest nun mit dem Qmodmaster auch mal einen Wert in eines unserer INPUT_xx Register schreiben. Mach das zum Beispiel so:
- 05.PNG (22.47 KiB) Viewed 11142 times
Hier schreibe ich also in das Register Nummer 4 (Adresse 3) den Wert 5431. Wenn ich jetzt dieses Register im PA auslesen will, kann ich das auf der Kommandozeile mit "piTest -r Input_4" machen. Die Anwort sieht so aus:
2 Byte-Value of Input_4: 5431 dez (=1537 hex)
Alternativ hätte ich auch mit piTest -r 17,2 auslesen können, wonach ich allerdings die beiden Bytes des registers einzeln angezeigt bekomme und sie dann selber zu einem Imtegerwert ausrechnen müsste:
55 21
Rechnung: 2 *256 + 55 = 5431
Übrigens könne ich mit dem Master über ein "Read Holding Registers das Register 4 (Adresse 3) wieder auslesen und bekäme dann eben 5431 angezeigt.
So weit Deine Modbus Slave Konfiguration. Wenn mit diesen Hilfsprogrammen alles läuft, dann solltest Du auch problemlos mit der SAIA PCD3 die Slaveregister auslesen können. Wie Du Prozesswerte mit Bash (also Kommandozeilen Scripte) in die Register hineinbekommst, habe ich Dir hier ja gezeit. Solche Scripte kann man dann auch zeitgesteuert untzer Linux starten lassen und z.B. alle Sekunde ausführen lassen. In unserem Tutorial für Modbus slave haben wir das an einem Beispiel gezeigt. Später zeige ich Dir, wie es unter Python genauso einfach geht. Node Red kannst Du getrost außen vor lassen, es erleichtert Deine Arbeit kaum sondern verkompliziert die Dinge eher, solange wir keine konfigurierbaren Knoten zum Auslesen des PA anbieten. Und da Du ja Python programmieren kannst, denke ich, Du solltest das ruhig in Python angehen. Aber dazu wie gesagt später.
Jetzt zur Masterseite:
Du ziehst in PiCtory den Modbus TCP Mater auf die Arbeitsfläche in den leeren Slot. In der WebStatus Konfiguration haben wir ihn ja bereits enabled.
Der Master kann nun zyklisch (und wirklich nur zyklisch) mit einem vorgebbaren Intervall Slaveregister beliebiger Slaves im gleichen TCP-Netzwerk abfragen. Je ein Mastermodul kann dabei nur einen einzigen Slave abfragen. In Deinem Fall hast Du ja auch nur einen Slave, nämlich das Wachendorfgateway, welcher die M-Bus Devices abfragt. Ich gehe davon uas, dass es mal eine IP 192.168.0.33 haben soll und über den Port 502 Modbusbefehle entgegen nimmt. Dann solltest Du das genau so in dem Werteeditor für den Master eingeben:
- 06PNG.PNG (11.49 KiB) Viewed 11142 times
Welche Modbusregister nun überwelchen befehl Datenaustauschen, dass konfigurierst Du über die "extended Data" Maske, die Du mit der rechten Maustaste über dem Modbus-Master (Kontextmenü) abrufen kannst. Ohne Dein Wachnedorf-Gateway zu kennen gehe ich hier einfach mal davon aus, dass die 5 M-Bus-Geräte jeweils 4 Modbus Register ab 1, 11, 21, 31, 41 und 51 belegen. Ich erzeuge daher 5 Aufgaben (Zeilen) inder erweiterten Konfiguration, die jeweils 4 Register abfragen und die Ergebnisse im Prozessabbild (PA) vom RevPi hinterlegen. Dafür stehen diesen Aufgaben 32 Input WORD-Werte (also 2 Byte breit) zur Verfügung. Würdest Du unter Modbus mit "Coils" arbeiten, dann ständen Dir dafür 32 Input BOOL Werte (1 Bit Breite) zur Verfügung. Für Ausgänge (also Werte, die Du zum Gateway (Slave) sendest, stehen nestrpechend 32 Output WORDs und 32 Output BOOLs im PA zur Verfügung. Um das ein wenig übersichtlicher zu halten, gehen wir nocheinmla zurück zum Werteeditor (unten rechts auf der PiCtorySeite). Dort veränern wir nun die Namen der Prozesswerte im PA so, dass wir sie später auch wiedererkennen:
Aus "Input_Word_1" mache ich "Hzg_U_Leistung"
Aus "Input_Word_2" wird "Hzg_U_Vorlauftemp"
bis "Input_Word_20", aus dem "Strom_I" wird.
Ab jetzt kannst Du überall mit diesen Namen arbeiten, statt mit den ziemlich ananymen Angaben "Input_Word_xx"
So machen wir das dann auch in der erweiterten Daten Konfiguration:
- 07PNG.PNG (25.68 KiB) Viewed 11142 times
Ich gehe hier von Voraussetzungen aus, die eventuell nicht stimmen. Diu musst hier die entsprechenden Einträge anpassen, wenn das Wachdorfgateway andere Vorgehensweisen erwartet:
Ich vernwende den Modbusbefehl "read input registers" mit den jeweiligen Startregistern 1, 11, 21, 31 und 41 uns lese alle 1000 ms jeweils 4 hintereinaderliegende Register die jeweiuls die Werte "Lesitung, Vorlauftemp, Rücklauftemp und Energie als 16 bit breite Datenworte in einem einzigen Register enthalten. Wenn bei Dir die Regioster so nicht angeordnet sind oder gar floatingpointwerte enthalten, musst Du natürlich völlig anders vorgehen!
Übrigens zwei Hinweise noch:
1) Die Spalte "UnitID" hat in der Regel unter Modbus TCP keine Bedeutung. Viele Geräte erwarten hier eine 255 oder aber manche auch eine 1. Das muss in den Unterlagen vom Wachendorfgerät stehen!
2) Wenn Du diese erweiterte Datenkonfiguration durcgeführt hast, solltest Du die Namen der PA-Werte nicht mehr abändern, sonst gehen die Zieladressen in den Aufgaben verloren oder noch schlimmer es wird in falsche PA-Adressen geschrieben.
Nach dieser Konfiguration wie immer unter "File" alles abspeichern und die Häkchen bei Export für die gewünschten PA-Werte setzen, um eine Offsetliste geenrieren zu können. Hierbei fällt Dir eventuell auf, dass es auch Werte mit der Bezeichnung "Modbus_Action_Status_x" gibt. Diese Werte auch einfach mal exportieren (ich habe sie sogar sinnvoll umbenannt). Sie dienen uns später dazu, per Software auch Fehler bei der Modbusabfrage zu erkennen und zum Beispiel beim Ausfall des Wachendorfgateways entsprechend eine Meldung an die übergeordnete Steuerung weitergeben zu können! Wenn hier ein Feherlstatus erscheint, müssen wir ihn entweder durch einen restart von PiCtory zurücksetzen oder aber gezielt durch das Beschreiben der Flags mit dem Namen "Action_Status_Reset_x" im PA (Hab ich ebenfalls sinnvoll umbenannt). Ich habe mal die Werte "Output_Word_x" auch alle mit einem Export-Haken versehen, falls Du Werte zum Gateway schicken willst...
Übrigens zu Diagnosezwecken exportiere ich auch mal "Master_Status_Reset" und "Modbus_Master_Status", die einen allgemeinen Status vom Master-Dienst melden und ein Rücksetzen erlauben.
Der Modbus Master - Teil meiner Offsetliste sieht nun so aus:
Code: Select all
Hzg_U_Leistung 142 //WORD
Hzg_U_Vorlauftemp 144 //WORD
Hzg_U_Rücklauftemp 146 //WORD
Hzg_U_Energie 148 //WORD
Ofen_Leistung 150 //WORD
Ofen_Vorlauftemp 152 //WORD
Ofen_Rücklauftemp 154 //WORD
Ofen_Energie 156 //WORD
Hzg_Leistung 158 //WORD
Hzg_Vorlauftemp 160 //WORD
Hzg_Rücklauftemp 162 //WORD
Hzg_Energie 164 //WORD
Solar_Leistung 166 //WORD
Solar_Vorlauftemp 168 //WORD
Solar_Ruecklauftemp 170 //WORD
Solar_Energie 172 //WORD
Strom_Verbrauch 174 //WORD
Strom_Leistung 176 //WORD
Strom_U 178 //WORD
Strom_I 180 //WORD
Modbus_Action_Hzg_u 210 //BYTE
Modbus_Action_Ofen 211 //BYTE
Modbus_Action_Hzg 212 //BYTE
Modbus_Action_Solar 213 //BYTE
Modbus_Action_Strom 214 //BYTE
Modbus_Master_Status 242 //BYTE
Output_Word_1 243 //WORD
Output_Word_2 245 //WORD
Output_Word_3 247 //WORD
Output_Word_4 249 //WORD
Output_Word_5 251 //WORD
Output_Word_6 253 //WORD
Output_Word_7 255 //WORD
Output_Word_8 257 //WORD
Output_Word_9 259 //WORD
Output_Word_10 261 //WORD
Output_Word_11 263 //WORD
Output_Word_12 265 //WORD
Output_Word_13 267 //WORD
Output_Word_14 269 //WORD
Output_Word_15 271 //WORD
Output_Word_16 273 //WORD
Output_Word_17 275 //WORD
Output_Word_18 277 //WORD
Output_Word_19 279 //WORD
Output_Word_20 281 //WORD
Output_Word_21 283 //WORD
Output_Word_22 285 //WORD
Output_Word_23 287 //WORD
Output_Word_24 289 //WORD
Output_Word_25 291 //WORD
Output_Word_26 293 //WORD
Output_Word_27 295 //WORD
Output_Word_28 297 //WORD
Output_Word_29 299 //WORD
Output_Word_30 301 //WORD
Output_Word_31 303 //WORD
Output_Word_32 305 //WORD
Action_Status_Reset_Hzg_U 311.0 //BOOL
Action_Status_Reset_Ofen 311.1 //BOOL
Action_Status_Reset_Hzg 311.2 //BOOL
Action_Status_Reset_Solar 311.3 //BOOL
Action_Status_Reset_Strom 311.4 //BOOL
Master_Status_Reset 315 //BYTE
Abschließend bitte noch PiControl neu starten (piTest -x oder in PiCtory unter "Tools" "Reste Driver" wählen.
Wenn Du die Wachdorf angeschlossen hast, dann solltest Du nun z.B. mit " piTest -r Ofen_Vorlauftemp" die Ausgabe der Vorlauftemperatur vom Ofen sehen:
2 Byte-Value of Ofen_Vorlauftemp: 0 dez (=0000 hex)
Bei mir natürlich 0 weil ich leider keinen Ofen und keine Wachdorf habe
Alternativ kannst den Wert auch über piTest -r 152,2 auslesen, bekommst aber 2 Byte zurückgegeben, aus denen Du Dir selber eine Integer berechnen musst.
Okay, nun haben wir auch den Master so weit durch. Also Zeit um zu zeigen, wie Du die Werte aus den PA-Werten vom Master in den Slave kopierst. Wenn Du sie dabei irgendwie manipulierne willst (Minimalwerte suchen, Durchschnitte bilden etc. dann ist das dabei naürlcih sehr leicht möglich. Es erfordert aber spezielöle Programmzeilen, die ich hier mal weglasse, weil ich ja absolut nicht wissen kann, was Du mit den Daten zwischen dem Kopieren so alles anstellen willst.
Hier der Code für ein Python script, welches die Einganswerte vom Master einfach zyklisch in den Slave kopiert. Ich gehe von einer Zykluszeit von 500 ms aus, weil wir beim Master eben einen Abfragezyklus von 1 Sekunde gewählt hatten. Somit werden wir garantiert alle Werte aus dem Wachendorf Gateway in unseren Slaveregistern aktuell bereithalten...
Code: Select all
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import time # used for the delays of the demo
# first the driver has to be opened by the "open" statement:
f = open("/dev/piControl0","wb+",0)
# now the endless loop of the demo starts ...
while 1:
f.seek(142) # here the offset within the process image is set: Modbus Master Input starts at byte 142 in the configuration
x = f.read(40) # 20 registers = 40 bytes are read
f.seek(75) # here the offset within the process image is set: Modbus Slave Output starts at byte 142 in the configuration
f.write(x) # 20 registers = 40 bytes are written
time.sleep(0.5)
Das ist im Prinzip alles. Ist doch ziemlich simpel, oder?
Wenn Du natürlich mit wechselnden Konfigurationen arbeitest und Dein Programm dann nicht immer mit den absoluten Offsets anpassen willst, dann wird es ein wenig komplizierter, denn um mit den symboilischen Namen zu operieren musst Du ioctl Aufrufe mit Objektstrukturen durchführen. In unserem Tutorial zum Zugriff auf das PA mit Python gibt es aber dazu ein Beispiel. Noch einfacher geht es aber mit der Bibliothek von Sven Sager (RevPiModIO), wobei Du dann aber mit Objekten gut umgehen können solltest.
Bei dem kurzen Code habe ich auch jede Fehlerbehandlung weggelassen. Wenn Du hier bei diesem Code zum Beispiel einfahc malö den Netzwerkstecker ziehst, wird das Programm sich höchst wahrscheinlich nicht von sleber wieder fangen. Dazu müsstest Du dann in der Endlosschliefe mit Try / Exceptions arbeiten und/oder die Statusworte und Flags im PA zyklisch abfragen und bei Fehlern eine entsprechende Fehelerbehandlung durchführen (z.B. selber den Modbus Dienst zurücksetzen über ein Bash-Kommand "piTest -x" welches im Python Script ausgelöst wird). Aber das überlasse ich dann mal Dir, denn das ist wirklich sehr von Deiner retslichen Hardware und deren Feherlverhalten abhängig.
Nun noch ien kurzer Ausblick auf Couldfähigkeit:
Wenn Du z.B. die KUNBUS Cloud verwendest, könntest Du die vorgenannten bash-Befehle alle in der Cloudoberfläche als zyklische Abfragen konfigurieren und so zyklisch alle Prozesswerte in die Cloud zu einem Dashboard oder in den Speicher für die historische Auswertung mit diversen Abfragetools befördern. Alternativ könntest Du auf der Cloudoberfläche aber auch einen anderen Weg wählen: Du würdest den Cloud-Agenten, der auf dem RevPi dann aktiviert ist, einen oder mehrere Linux-Sockest üvberwachen lassen. sobald Du in so einen Socket etwas reinschreibst, würde der Agent diesen Wert in die zugeordnete Cloudtabelle schieben. Ich habe das hier mal beispielhaft für Hzg_U in den Code eingebaut und gehe davon aus, dass dafür ein Unix-Socket "/var/run/Hzg_U_values.sock" über die Cloudoberfläche in dem RevPi Core erzeugt wurde:
Code: Select all
# -*- coding: utf-8 -*-
import time # used for the delays of the demo
import socket
import struct
# first the driver has to be opened by the "open" statement:
f = open("/dev/piControl0","wb+",0)
clientHzg_U_values = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
clientHzg_U_values.connect("/var/run/Hzg_U_values.sock")
# now the endless loop of the demo starts ...
while 1:
f.seek(142) # here the offset within the process image is set: Modbus Master Input starts at byte 142 in the configuration
x = f.read(40) # 20 registers = 40 bytes are read
f.seek(75) # here the offset within the process image is set: Modbus Slave Output starts at byte 142 in the configuration
f.write(x) # 20 registers = 40 bytes are written
# get the 4 process values form the input byte-string by using the struct library
Leistung = struct.unpack_from('<H',x[0])[0] # unpack returns a tuple even is only one value is returned!
Vorlauftemp=struct.unpack_from('<H',x[2])[0] # use 2 byte offset to get the second register value
Ruecklauftemp=struct.unpack_from('<H',x[4])[0] # use 4 byte offset to get the third register value
Energie=struct.unpack_from('<H',x[6])[0] # use 6 byte offset to get the fourth register value
#write the 4 process values as 1 line inot the linux socket (CSV format)
clientPiececountBox.send((str(Leistung) + ", " +
str(Vorlauftemp) + ", " +
str(Ruecklauftemp) + ", " +
str(Energie) + "\n"
).encode('utf-8')
)
time.sleep(0.5) #loop every 1/2 second
So. Das war ein ziemlich lange Antwort, aber ichhoffe, dass Du damit nun besser klar kommst. Was Deine Probleme mit dem grauen Bildschirm bei PiCtory anbelangt, so hoffe ich, dass mit sudo upgrade die Probleme weg sind. Ansonsten versuche es bitte mal mit einem anderen Browser. Wir haben insbesondere bei älteren IE Browsern so was schon mal gesehen...
Viel Erfolg!