Byte-order Problem

Für Themen rund um das Prozessabbild des RevPi Core
Post Reply
fbarthez
Posts: 3
Joined: 15 Oct 2021, 14:22
Answers: 0

Byte-order Problem

Post by fbarthez »

Hallo zusammen,

mit einem RevPi Compact möchte ich Werte aus einem ABB B21 Stromzähler über Modbus RTU auslesen. Testweise hatte ich dafür node-red verwendet und bekam die erwarteten Werte angezeigt.

Nun habe ich über PiCtory einen virtuellen Modbus RTU master angelegt und lese Register mit verschiedenen Datentypen aus. Der Wert der Frequenz (1 Register, unsigned short) bereitet keine Probleme. Sobald der Wert allerdings zwei oder vier Register belegt, bekomme ich ein byte-order Problem: aus [00 00 08 fc] wird bspw. [00 00 fc 08].

Im Einzelnen: In node-red bekomme ich für die Spannung in Zehntel Volt (2 Register, unsigned integer) als Antwort einen buffer mit [00 00 08 fc], also 2300.
Lese ich via "piTest -r voltage_l1_2" nur das zweite Register aus, erhalte ich: "2 Byte-Value of voltage_l1_2: 2300 dez (=08fc hex)"
Lese ich jedoch die 4 Bytes des Werts mit "piTest -r 48,4,h" aus, so erhalte ich [00 00 fc 08]. High byte und low byte sind hier also miteinander vertauscht.

Diese Reihenfolge taucht dann auch in revpimodio2 auf. Dort habe ich die Byteorder verändert (little/big), die bezieht sich aber offenbar auf die Wortfolge, nicht auf die Bytes innerhalb eines Worts.

Was mache ich falsch? (Das gleiche Problem taucht glaube ich hier einmal auf: https://revolutionpi.de/forum/viewtopic ... der#p10754)

Bin für alle Hinweise dankbar…

P.S.: Die Anleitung des ABB B21 schreibt zum Thema: "For quantities that are represented as more than 1 register, the most significant byte is found in the high byte of the first (lowest) register. The least significant byte is found in the low byte of the last (highest) register." Das deckt sich mit dem, was ich in node-red und auf der seriellen Schnittstelle sehe:

Code: Select all

> 2021/10/15 14:02:37.654294  length=8 from=40 to=47
 02 03 5b 00 00 02 d7 1c                          ..[.....
--
< 2021/10/15 14:02:37.693937  length=8 from=45 to=52
 02 03 04 00 00 08 fc ce                          ........
User avatar
nicolaiB
KUNBUS
Posts: 869
Joined: 21 Jun 2018, 10:33
Answers: 7
Location: Berlin
Contact:

Re: Byte-order Problem

Post by nicolaiB »

Hallo fbarthez,

kannst du deinen Python bzw. die relevanten Auszüge darauf teilen?

Gruß Nicolai
fbarthez
Posts: 3
Joined: 15 Oct 2021, 14:22
Answers: 0

Re: Byte-order Problem

Post by fbarthez »

Hallo Nicolai,

danke für die prompte Antwort! An python code gibt's nicht viel zu sehen:

Code: Select all

# -*- coding: utf-8 -*-
import revpimodio2
import struct

def cycle(ct):

    print("raw voltage l1", rpi.io.raw_voltage_l1.value.hex())

    print("raw frequency", rpi.io.raw_frequency.value.hex())

    raw_voltage_l1 = struct.unpack(">1L", rpi.io.raw_voltage_l1.value)
    VOLTAGE_L1 = raw_voltage_l1[0]
    print("VOLTAGE_L1", VOLTAGE_L1)

    raw_frequency = struct.unpack("1h", rpi.io.raw_frequency.value)
    FREQUENCY = raw_frequency[0]
    print("FREQUENCY", FREQUENCY)

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

rpi.io.voltage_l1_1.replace_io("raw_voltage_l1", "4s")

rpi.io.frequency.replace_io("raw_frequency", "2s")

rpi.cycleloop(cycle, cycletime=1000)
Beispielhaftes output:

Code: Select all

raw voltage l1 0000fc08
raw frequency 8a13
VOLTAGE_L1 64520
FREQUENCY 5002
raw voltage l1 0000fc08
raw frequency 8a13
VOLTAGE_L1 64520
FREQUENCY 5002
raw voltage l1 0000fb08
raw frequency 8a13
VOLTAGE_L1 64264
FREQUENCY 5002
Den geschilderten Effekt bekomme ich auch, wenn ich mein "program.py" entferne und einfach nur revpipyload mit Einstellungen über /etc/revpipyload/replace_ios.conf versorge:

Code: Select all

[current_l1]
replace = current_l1_1
frm = L
byteorder = big

[voltage_l1]
replace = voltage_l1_1
frm = L
byteorder = big
Der Vollständigkeit halber hier noch die Versionen der Pakete – alle heute frisch aktualisiert und neu gestartet:
pictory 2.0.3
pimodbus-master 1.0.9-1
revpipyload 0.9.6-1+revpi10
Last edited by fbarthez on 15 Oct 2021, 17:14, edited 1 time in total.
User avatar
nicolaiB
KUNBUS
Posts: 869
Joined: 21 Jun 2018, 10:33
Answers: 7
Location: Berlin
Contact:

Re: Byte-order Problem

Post by nicolaiB »

Hallo fbarthez,

Ich habe es reproduzieren können und werde die Ursache genauer untersuchen.

Gruß Nicolai
fbarthez
Posts: 3
Joined: 15 Oct 2021, 14:22
Answers: 0

Re: Byte-order Problem

Post by fbarthez »

Hallo Nicolai,
nicolaiB wrote: 15 Oct 2021, 17:56 Ich habe es reproduzieren können und werde die Ursache genauer untersuchen.
sehr fein, vielen Dank! Wenn Du noch Infos oder Zugriff auf unsere Testinstallation brauchst, sag gerne Bescheid!
User avatar
nicolaiB
KUNBUS
Posts: 869
Joined: 21 Jun 2018, 10:33
Answers: 7
Location: Berlin
Contact:

Re: Byte-order Problem

Post by nicolaiB »

Hallo fbarthez,

ich habe mir deinen Anwendungsfall mal etwas genauer angeschaut und es hier nachgebaut. Wie du schon richtig erkannt hast unterscheidet sich die Reihenfolge der jeweiligen Bytes in Device und im Prozessabbild. Der RevPi basiert auf eine little endian Architektur und speichert daher alle Daten im Prozessabbild auch in der dementsprechenden Reihenfolge. Werkzeuge wie piTest oder auch die Python Bibliothek berücksichtigen entsprechende Umwandlungen im Hintergrund, sodass es in den meisten Fällen vollkommen transparent im Hintergrund passiert (z.B. wenn du das Register direkt über den Namen addressierst).

Ein besonderer Fall ist das lesen von (Modbus-) Werten, welche über die Länge des entsprechenden Registers hinaus gehen. Pro Modbus-Register wird im Prozessabbild ein WORD genutzt. Liest du nun also 2 Modbus-Register aus - wie in deinem Fall für die Spannung - werden die Daten in das in der Konfiguration hinterlegte Register im Prozessabbild, sowie das darauf folgende geschrieben. Liest du diese diese Register einzeln aus, wird die Umwandlung wieder transparent im Hintergrund ausgeführt (siehe z.B. piTest -r voltage_l1_2). Da jedoch keinerlei Zusammengehörigkeit einzelner Register im Prozessabbild hinterlegt ist, weiß piTest bzw. revpimodio nicht, dass um den Wert korrekt zu decodieren eigentlich das erste und zweite Register getauscht werden müssten. Das kannst du jedoch einfach in deinem Pythoncode berücksichtigen. Ein Beispiel wie eine entsprechende Funktion aussehen könnte, zeigt das folgende Beispiel:

Code: Select all

#!/usr/bin/env python3

import struct
import revpimodio2

class ModIOExample:
    def __init__(self):
        self.__rpi = revpimodio2.RevPiModIO(autorefresh=True, monitoring=True)
        self.__rpi.handlesignalend()

        self.__rpi.io.Input_1.replace_io("Input_DWORD", frm="4s")
        self.__rpi.io.Input_DWORD.reg_event(self.dword_event_handler)

    def run(self):
        self.__rpi.mainloop()

    def dword_event_handler(self, name, value_raw):
        value_pa = bytearray(value_raw)
        value = struct.unpack("<L", bytearray([value_pa[2], value_pa[3], value_pa[0], value_pa[1]]))[0]

        print(f"DWORD value: {value} ({hex(value)})")


example = ModIOExample()
example.run()

Ich hoffe ich konnte es dir einigermaßen verständlich beschreiben. Solltest du noch Fragen haben sag gerne Bescheid.

Gruß Nicolai
fbarthez
Posts: 3
Joined: 15 Oct 2021, 14:22
Answers: 0

Re: Byte-order Problem

Post by fbarthez »

Hallo Nicolai,

vielen Dank für die ausführliche Antwort und den Beispielcode!

Für mich sieht es so aus, als wäre das ein generisches Problem, das sich eigentlich gut über revpimodio lösen ließe? Testweise habe ich mal in io.py von revpimodio2 die StructIO.get_structvalue() Methode umgebaut:

Code: Select all

    def get_structvalue(self):
        """
        Gibt den Wert mit struct Formatierung zurueck.

        :return: Wert vom Typ der struct-Formatierung
        """
        if self._bitshift:
            return self.get_value()
        else:
            four_byte = rematch("^.[fiIlL]$", self.__frm)
            eight_byte = rematch("^.[dqQ]$", self.__frm)
            if four_byte is not None:
                value_pa = bytearray(self.get_value())
                shuffled_value = bytes(bytearray([value_pa[2], value_pa[3], value_pa[0], value_pa[1]]))
                return struct.unpack(self.__frm, shuffled_value)[0]
            elif eight_byte is not None:
                value_pa = bytearray(self.get_value())
                shuffled_value = bytes(bytearray([value_pa[6], value_pa[7], value_pa[4], value_pa[5], value_pa[2], value_pa[3], value_pa[0], value_pa[1]]))
                return struct.unpack(self.__frm, shuffled_value)[0]
            else:
                return struct.unpack(self.__frm, self.get_value())[0]
Für meine Anwendungsfälle kann ich dann die Werte einfach über replace_ios.conf konfigurieren und alles läuft wie erwartet. Wäre es sinnvoll, dieses Verhalten über replace_ios.conf konfigurierbar zu machen?

Mein Ziel ist letzten Endes, aus einer Tabelle mit Geräte-Adressen und Register-Daten automatisiert die Konfiguration für piCtory und replace_ios.conf zu erzeugen, ohne weiteren Code schreiben zu müssen.

Erst mal ein schönes Wochenende!
User avatar
nicolaiB
KUNBUS
Posts: 869
Joined: 21 Jun 2018, 10:33
Answers: 7
Location: Berlin
Contact:

Re: Byte-order Problem

Post by nicolaiB »

Hallo fbarthez

Frei nach Radio Eriwan: Im Prinzip ja, aber... Die Notwendigkeit hier die Bytes umzudrehen ist spezifisch für die virtuellen Modbus Devices und hängt mit der Art zusammen wie mehrere Register geschrieben werden (siehe meinen voherigen Post). Wenn ich z.B. ein Profinet IRT Gateway nutze und Daten als REAL oder DINT stimmt die Byte-Reihenfolge (Bytes werden von der Steuerung direkt geschrieben) und eine Umwandlung wie von dir vorgeschlagen würde die Bytes wieder mixen, sodass dann hier das Eingangs von dir beschriebene Problem auftreten würde. Auch eine Anpassung der Modbus devices ist nicht so einfach möglich, da das Einlesen von mehreren Register ja entweder dazu genutzt wird einzelne Register (je WORD ein Wert) in Serie einzulesen oder aufgeteilte Werte (mehrere WORDs pro Wert) einzulesen und eine solche Umwandlung nur im letztgenannten Fall notwendig ist.

Eine Lösung könnte sein den Parameter byteorder um zwei weitere Varianten zu ergänzen (die Varianten heißen mid-little-endian und mid-big-endian) und an dieser Stelle die ggf. notwendigen Umwandlungen vorzunehmen.

Gruß Nicolai
Post Reply