Viele Modbus-Werte gleichzeitig lesen

Arbeitet man in piCtory mit den virtuellen Modbus Devices, stößt man manchmal auf Probleme mit der Speicherzuweisung.

In piCtory wählt man die Modbus-Register und die Anzahl aus, die von einem Modbus-Teilnehmer abgeholt werden sollen. Diese Bytes werden dann zu einem IO im Prozessabbild zugewiesen.

Jeder IO ist lediglich 1 Wort = 2 Bytes groß. Werden nun also mehrere Register abgeholt (in unserem Beispiel 32 = 64 Byte) werden diese Bytes einfach über den Speicherbereich im Prozessabbild der nachfolgenden IOs überschrieben!!! Leider löscht piCtory die nachfolgenden IOs nicht und es wäre möglich weitere Register in einen bereits überschriebenen IO zu mappen.

Hier ist große Vorsicht geboten, da die Daten sonst „Müll“ sind!

In unserem Beispiel beinhalten die 64 Byte 16 float-Werte (je Wert 4 Byte). Diese werden wir mit RevPiModIO relativ komfortabel erhalten:

# -*- coding: utf-8 -*-
"""
Viele Modbuswerte vereinfacht lesen.

In piCtory ist auf einem Modbus Master Modul eine Aktion hinterlegt:
Action ID | Unit ID |    Function Code       | Register Addr. | Quantity of Reg. | Action Int. | Device Value
   1      |    1    | READ_HOLDING_REGISTERS |        2       |         32       |    900      | Input_Word_1

Diese Aktion holt 32 Wörter = 64 Bytes von einem Modbus-Teilnehmer und setzt diesen Wert bei Input_Word_1 ein. Da
dieser Input nur 1 Wort = 2 Byte lang ist, werden die nachfolgenden Inputs von dieser Aktion auch überschrieben,
aber nicht gelöscht.

Die Werte von dem Modbus-Teilnehmer sind 16 float-Werte (4 Byte pro Wert).
"""
import revpimodio2
import struct


def cycle(ct):
    """
    Zyklusfunktion für Demonstration.

    Zu Beginn des Zyklus werden die 16 float-Werte aus den 64 Bytes entpackt
    und stehen als <class 'tuple'> zur Verfügung. Danach weisen wir diese
    unseren "richtigen" Variablen hinzu.
    """

    # Die 64 Byte in 16 float-Werte entpacken
    my_floats = struct.unpack("16f", rpi.io.all_float_values.value)

    # Werte des Tuples können nun frei verwendet werden
    L_N_A2_U_L1_N = my_floats[0]
    L_N_A2_U_L2_N = my_floats[1]
    L_N_A2_U_L3_N = my_floats[2]
    L_N_A2_U_L1_L2 = my_floats[3]
    L_N_A2_U_L2_L3 = my_floats[4]
    L_N_A2_U_L3_L1 = my_floats[5]
    L_N_A2_I_L1 = my_floats[6]
    L_N_A2_I_L2 = my_floats[7]
    L_N_A2_I_L3 = my_floats[8]
    L_N_A2_S_L1 = my_floats[9]
    L_N_A2_S_L2 = my_floats[10]
    L_N_A2_S_L3 = my_floats[11]
    L_N_A2_P_L1 = my_floats[12]
    L_N_A2_P_L2 = my_floats[13]
    L_N_A2_P_L3 = my_floats[14]
    L_N_A2_Q_L1 = my_floats[15]

    # Zum Testen einfach ausgeben:
    print("L_N_A2_U_L1_N", L_N_A2_U_L1_N)
    print("L_N_A2_U_L2_N", L_N_A2_U_L2_N)
    print("L_N_A2_U_L3_N", L_N_A2_U_L3_N)
    print("L_N_A2_U_L1_L2", L_N_A2_U_L1_L2)
    print("L_N_A2_U_L2_L3", L_N_A2_U_L2_L3)
    print("L_N_A2_U_L3_L1", L_N_A2_U_L3_L1)
    print("L_N_A2_I_L1", L_N_A2_I_L1)
    print("L_N_A2_I_L2", L_N_A2_I_L2)
    print("L_N_A2_I_L3", L_N_A2_I_L3)
    print("L_N_A2_S_L1", L_N_A2_S_L1)
    print("L_N_A2_S_L2", L_N_A2_S_L2)
    print("L_N_A2_S_L3", L_N_A2_S_L3)
    print("L_N_A2_P_L1", L_N_A2_P_L1)
    print("L_N_A2_P_L2", L_N_A2_P_L2)
    print("L_N_A2_P_L3", L_N_A2_P_L3)
    print("L_N_A2_Q_L1", L_N_A2_Q_L1)


rpi = revpimodio2.RevPiModIO(autorefresh=True)
rpi.handlesignalend()

# Wir verwenden "s64", was diesen IO zu einem reinen, 64 byte langen <class 'bytes'> IO macht!
rpi.io.Input_Word_1.replace_io("all_float_values", "64s")

# Zyklusfunktion starten mit 1000 ms Zukluszeit
rpi.cycleloop(cycle, cycletime=1000)

Wie man sehen kann, ist das eigentliche Programm nur ein paar Zeilen, das meiste ist die Zuweisung und die Ausgabe der 16 float-Werte.

Natürlich könnte man auch mit dem .mainloop() arbeiten und ein Event an den neuen .all_float_values hängen. Dann würde man die struct.unpack(...) dort mit der iovalue machen.