4-Byte-Floats per Modbus TCP auslesen

Moderator: RevPiModIO

Post Reply
Ladam
Posts: 16
Joined: 30 Jul 2019, 09:59

4-Byte-Floats per Modbus TCP auslesen

Post by Ladam »

Hallo zusammen!

Ich probiere eine Energie-Messgerät per virtuellem Modbus TCP-Master auszulesen. Dazu müssen jeweils 2 Register ausgelesen werden und das Messgerät liefert die Daten im Float-Format.:
2Register.PNG
2Register.PNG (180.44 KiB) Viewed 15122 times
Um zu überprüfen, ob die Messdaten ins Prozessabbild geschrieben werden habe ich piTest benutzt. Allerdings liefert piTest ja nur einen 2-Byte-Wert. Da ich aber 2 Register auslese erwarte ich doch 4-Byte-Werte.
Das selbe Ergebniss bekomme ich wenn ich die Werte über revpimodio2 auslese:
Codebeispiel.PNG
Codebeispiel.PNG (14.19 KiB) Viewed 15122 times
Die Werte machen dementsprechend keinen Sinn.

Nun meine eigentliche Frage: Wie komme ich nun an die 4-Byte-Floats die mir das Messgerät liefert?

Über Quantity of Registers = 2 stelle ich doch im Prinzip 4 Byte ein oder nicht?

Schonmal Danke und viele Grüße!
Luke
User avatar
RevPiModIO
KUNBUS
Posts: 335
Joined: 20 Jan 2017, 08:44
Contact:

Re: 4-Byte-Floats per Modbus TCP auslesen

Post by RevPiModIO »

Hi Luke! Soweit alles richtig, aber RevPiModIO kann noch nicht wissen, dass es float sein soll.

Die Länge des IOs sollte 4 Byte sein und müsste nun mit .replace_io(...) ersetzt werden (leider kann man die alten Namen für neue IOs nicht verwenden - wäre evtl. mal ein interessantes Feature für die Zukunft).

Probiere noch einmal für einen IO beispielsweise:

Code: Select all

rpi.io.L_N_A1_U_L1_N.replace_io("l_n_a1_u_l1_n", "f")
print(rpi.io.l_n_a1_u_l1_n.value)

Evtl. müsstest du bei replace_io noch mit der byteorder spielen, diese ist default "little", könnte aber auch "big" sein, je nachdem wie deine Daten kommen.
https://revpimodio.org/doku2/io/#replace_io

Gruß, Sven
python3-RevPiModIO - https://revpimodio.org/ || Der RevPi ist das Beste, was passieren konnte!
Ladam
Posts: 16
Joined: 30 Jul 2019, 09:59

Re: 4-Byte-Floats per Modbus TCP auslesen

Post by Ladam »

Hallo Sven,

vielen Dank für deine schnelle Unterstützung!
Das werde ich im Laufe des Tages ausprobieren und sage Bescheid ob alles geklappt hat.

Grüße
Luke
Ladam
Posts: 16
Joined: 30 Jul 2019, 09:59

Re: 4-Byte-Floats per Modbus TCP auslesen

Post by Ladam »

Hallo Sven,

Ich habe das mal probiert, allerdings ohne Erfolg. Wenn ich den zweiten IO ersetzen will, wird dieser nicht gefunden, obwohl er ja im Modbus Master angegeben wurde.
Replace.PNG
Replace.PNG (197.18 KiB) Viewed 15081 times
Der replace einzeln funktioniert. (jedoch nicht mit den erwarteten Values, egal ob "big" oder "little"-Endian). Hier könnte es allerdings sein das ich die Bytes nochmal umsortieren muss, da ja soweit ich weis der Revpi die einzelnen Words schon zu little umsortiert.

Um das Prinzip zu testen habe ich noch folgendes probiert:
Replace3.3.PNG
Replace3.3.PNG (99.38 KiB) Viewed 15081 times
Wieder wird der 2te auszutauschende IO nicht erkannt. Mit piTest findet er alle test[1-4] Variablen.

Übersehe ich hier wieder etwas offensichtliches?

Vielen Dank für deine Hilfe!

Grüße
Luke
Ladam
Posts: 16
Joined: 30 Jul 2019, 09:59

Re: 4-Byte-Floats per Modbus TCP auslesen

Post by Ladam »

Hallo Sven,

zum Verständnis kommt hier noch die Schnittstellenspezifikation des Herstellers:
Spezifikation.PNG
Spezifikation.PNG (36.81 KiB) Viewed 15081 times
Diese Werte probiere ich unter anderem auszulesen. Eine Eintrag über little oder big gibt es nicht - hier werde ich ausprobieren. Warum die replace_io Funktion die 2te und darauf folgende Variablen nicht findet weis ich allerdings nicht.

Danke für deine Zeit und
Grüße
Luke
Ladam
Posts: 16
Joined: 30 Jul 2019, 09:59

Re: 4-Byte-Floats per Modbus TCP auslesen

Post by Ladam »

Hallo zusammen,

ich habe den "Fehler" gefunden. ""weil es evtl. gewollt passiert: Auch wenn man Quantity of Registers auf >1 setzt, wird für die Variable trotzdem nur 2 Byte (WORD) im Prozessabbild reserviert. Das lässt sich durch den durch Export gezeigten Offset der Variablen erkennen. Mit Verwendung eines Float-Konverters den ich hier im Forum gefunden habe konnte ich mein Problem mit Hilfe von import struct lösen.
Mit folgendem Code kann man dann auf die gewollten Register zugreifen, wobei der Parameter von readFloat32 der Offset der Variable im Prozessabbild ist. Da sich die benötigten Messwerte in meinem Anwendungsfall alle hintereinander befinden, alles 4-Byte-Floats sind und ich alle innerhalb eines Zyklus abfragen will, reicht eine Modbusanfrage aus:
Beispiel2.PNG
Beispiel2.PNG (31.17 KiB) Viewed 15043 times

Code: Select all

#!/usr/bin/python3

import fcntl
import struct
import time
import numpy as np
import math



f = open("/dev/piControl0","wb+",0);

def filterNan(value):
    
    if math.isnan(value)== False:
         return value


def readFloat32(offset): 

    f.seek(offset);
    x = f.read(2);
    print(x)
    a = struct.unpack('<H',x)
    print(a)
    byte0 = np.int_(a[0]);

    f.seek(offset+2);
    x = f.read(2);
    b = struct.unpack('<H',x);
    byte1 = np.int_(b[0]);
    
    volt = floatConverter(inputArray = [byte0, byte1]);
    
    return volt


def floatConverter(inputArray):
    int32Val = np.int_(inputArray[1] + (inputArray[0] << 16)); 
    newFloat = struct.unpack('f', struct.pack('i', int32Val))[0]
    return newFloat

L_N_A2_U_L1_N = (readFloat32(802))
L_N_A2_U_L2_N = (readFloat32(806))
L_N_A2_U_L3_N = (readFloat32(810))
L_N_A2_U_L1_L2 = (readFloat32(814))
L_N_A2_U_L2_L3 = (readFloat32(818))
L_N_A2_U_L3_L1 = (readFloat32(822))
L_N_A2_I_L1 = (readFloat32(826))
L_N_A2_I_L2 = (readFloat32(830))
L_N_A2_I_L3 = (readFloat32(834))
L_N_A2_S_L1 = (readFloat32(838))
L_N_A2_S_L2 = (readFloat32(842))
L_N_A2_S_L3 = (readFloat32(846))
L_N_A2_P_L1 = (readFloat32(850))
L_N_A2_P_L2 = (readFloat32(854))
L_N_A2_P_L3 = (readFloat32(858))
L_N_A2_Q_L1 = (readFloat32(862))
-edit- ich weiß nicht warum das Codefenster so seltsam aussieht.
Da ich es jetzt ohne RevPiModIO gelöst habe steht der Beitrag vermutlich im falschen Forum... außer es gibt eine Möglichkeit, es damit zu lösen (und ich bin sicher die gibt es). Man könnte es denke ich auch mit der replace_io Funktion lösen, muss dann aber allerdings jede 2te Variable "auslassen", damit in dem hier vorliegendem Fall (4-byte-Float) keine Probleme hinsichtlich Offset zustande kommen. Bei der replace_io Funktion werden anscheinend ebenfalls die nächsten Variablen im Prozessabbild überschrieben, wenn der Datentyp >2 Byte ist.

Sven, falls du noch eine andere Möglichkeit kennst, oder du die replace_io Funktion anpassen willst, sodass sie "dynamisch", z.B. durch die Angabe Quantity of Registers, erkennt, wie groß eine Variable sein muss, lass es mich und die Community bitte Wissen. Ich denke ich bin nicht der Erste der mit diesem Problem zu kämpfen hatte.

Gruß
Luke
User avatar
RevPiModIO
KUNBUS
Posts: 335
Joined: 20 Jan 2017, 08:44
Contact:

Re: 4-Byte-Floats per Modbus TCP auslesen

Post by RevPiModIO »

Hi Luke!

Endlich finde ich die Zeit um mich um dieses Anliegen zu kümmern - Sorry für das Werten!

Generell hast du ja schon alles selber herausgefunden. In piCtory wählt man die Modbus-Register aus und einen IO (1 Wort = 2 Byte), und wenn es mehr als 1 Register ist, dann wird der Wert einfach über die dahinterliegenden IOs drüber geschrieben. In deinem ersten Fall wären die Werte also alle "müll" gewesen, da die hinteren zwei Byte von dem nächsten IO mit den vorderen 2 Byte überschrieben worden sind.

Auch was RevPiModIO angeht hast du es genau auf den Punkt gebracht! Wenn man .replace_io(...) verwendet und der IO, auf den man die Funktion anwendet, nur zwei Byte lang ist, das gewählt Format aber 4 Byte lang sein soll (float-Werte) dann löscht RevPiModIO die dahinterliegenden Bytes. Damit kann der Benutzer nicht aus versehen falsche IOs verwenden, die es eigentlich gar nicht gibt (diesen Mechanismus hat piCtory leider nicht).

Wenn du nun aus Leistungsgründen oder all den anderen Funktionen RevPiModIO verwenden magst, dann könntest du dies sehr komfortabel wie folgt tun:

Code: Select all

# -*- 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)
 
Es wäre cool, wenn du das mal durchspielen könntest, ob dann richtige Werte kommen :D

Gruß, Sven
python3-RevPiModIO - https://revpimodio.org/ || Der RevPi ist das Beste, was passieren konnte!
Post Reply