Pymodbus Reference Number "address" abfrage im Server

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
McAce_2
User
Beiträge: 11
Registriert: Donnerstag 12. März 2020, 09:30

Hallo Python Community,

ich habe ein Problem, der ModbusClient fragt spezielle Addressbereiche ab und auf diesen Adressbereich möchte ich im ModbusTCP Server reagieren.

Beispiel aus einem Wireshark log
Functionscode 3
Reference Number 1050
Word Count 100


das heißt ab "adresse" 1050 lese 100 Wörter, ich würde gerne im Pymodbus server die Zahl 1050 abgreifen, finde aber im Moment keine Möglichkeit diese Zahl zu bekommen.
Ich benötige die 1050 um darauf zu reagieren und ein zusätzliches Ereignis auszulösen.

Vielen Dank und viele Grüße,

McAce_2
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Und deinen Code können wir wo bewundern? Denn ohne den wird es schwer zu erraten, welche der diversen Server du benutzt.
McAce_2
User
Beiträge: 11
Registriert: Donnerstag 12. März 2020, 09:30

Sorry vergessen, leider habe ich auf den Client keinen Einfluss, die Kommunikation zwischen Client und Server ist stabil und funktioniert auch.
Mir geht es halt um das reagieren auf einen bestimmten Adressbereich

Code: Alles auswählen

class ProtocolData:

    def get_parameter_list(self):


        parameter_list = [e for e in range(0,1550)]



        return parameter_list

class ModbusServerTCP:
    """
        Service to receive data from ModbusClient, will run on feeder
        :author: Patrick Litzbarski
        """

    def __init__(self):
        self.identity = ModbusDeviceIdentification()
        self.identity.VendorName = 'Pymodbus'
        self.identity.ProductCode = 'PM'
        self.identity.VendorUrl = 'http://github.com/riptideio/pymodbus/'
        self.identity.ProductName = 'Pymodbus Server'
        self.identity.ModelName = 'Pymodbus Server'
        self.identity.MajorMinorRevision = '1.0'

        # self.address = '10.13.0.130'
        self.address = 'localhost'
        self.port = 5020

        self.interval = 1


    def set_parameter_object(self, parameter_wrapper):
        self.parameter_wrapper = parameter_wrapper


    def set_parameter_object_2(self, parameter_wrapper):
        parameter_wrapper.live_data = self.parameter_wrapper.live_data
        self.parameter_wrapper = parameter_wrapper


    def run_server(self):
        # Holding Register (ir) FC4  (Address, [content]*number)
        # codes
        # 1 = c # 2 = d # 3 = h # 4 = i # 5 = c # 6 = h # 15 = c # 16 = h
        # h = 16 ,6, 3 # i = 4 # c = 5,  # d = 2

        # parameter_list = ProtocolData().get_parameter_list(self.parameter_wrapper_null)
        parameter_list = ProtocolData().get_parameter_list()

        store = ModbusSlaveContext(
            di=ModbusSequentialDataBlock(0, [0]*1000),
            co=ModbusSequentialDataBlock(0, [0]*1000),
            ir=ModbusSequentialDataBlock(0, [0]*1000),
            hr=ModbusSequentialDataBlock(0, parameter_list))

        context = ModbusServerContext(slaves=store, single=True)

        server = ModbusTcpServer(context, identity=self.identity,
                                 address=(self.address, self.port))

        # print(f'store Values {store.getValues(4,0,100)}')

        print(f'framer {server.framer}')

        t = threading.Thread(target=server.serve_forever, daemon=True)
        t.start()

        loop = LoopingCall(f=self.updatevalues, a=server)
        loop.start(self.interval, now=True)

        reactor.run()

    def updatevalues(self, a):
        start = time.time()
        print("------------START----------")
        server_context = a.context[0x01]


        print("-------------END-------------")
        end = time.time()
        print("Zeit %s" % str(end - start))
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Seufz. Und ohne importe ist das jetzt genau welche Implementierung? Es gibt ja nur gefuehlt 20.
Sirius3
User
Beiträge: 17758
Registriert: Sonntag 21. Oktober 2012, 17:20

Dieses ganze get_parameter-Zeug sieht gar nicht nach Python aus.
McAce_2
User
Beiträge: 11
Registriert: Donnerstag 12. März 2020, 09:30

Okay ich dachte die sind nicht so wichtig

Code: Alles auswählen

from pymodbus.server.sync import StartTcpServer, ModbusTcpServer
from pymodbus.client.sync import ModbusTcpClient as ModbusClient

from pymodbus.device import ModbusDeviceIdentification
from pymodbus.datastore import ModbusSequentialDataBlock
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
McAce_2
User
Beiträge: 11
Registriert: Donnerstag 12. März 2020, 09:30

Sirius3 hat geschrieben: Donnerstag 12. März 2020, 12:29 Dieses ganze get_parameter-Zeug sieht gar nicht nach Python aus.
Klar das ist nach den Getter und Setter Prinzip von Java benannt, was stört den daran bzw. was ist der bessere oder Python Weg das zu machen?
McAce_2
User
Beiträge: 11
Registriert: Donnerstag 12. März 2020, 09:30

Ich gehe noch etwas mehr auf dem Problem ein.

Mit der Adresse 16 wird ein Wert in die Variable x geschrieben aber mit der Adresse 1060 wird auch der Wert in Adresse x geschrieben. Wenn jetzt nur die Adresse 16 geschrieben wird z.B 50

Code: Alles auswählen

x= server_context.getValues(3, 16, 2)
dann wird x durch

Code: Alles auswählen

x = server_context.getValues(3, 1060, 2)
überschrieben

Meine Idee war jetzt die 1060 abzufangen
und sowas wie

Code: Alles auswählen

if addresse == 1060:
   x = server_context.getValues(3, 1060, 2)
zu verwenden.

Fragt nicht nach Sinn das ist so gegeben und da kann ich nichts dran ändern :-(

Ich vermute mal das das mit den updatevalues() nicht so optimal ist, nur wüsste ich nicht wie ich sonst aus dem Modbus die Werte in die Variable x bekomme.
Sirius3
User
Beiträge: 17758
Registriert: Sonntag 21. Oktober 2012, 17:20

Den Sinn sollte man schon irgendwie verstehen, was wird denn mit dem x gemacht und wie sieht der komplette Code dazu aus? Wenn man will, dass etwas nicht überschrieben wird, dann benutzt man eine andere Variable.
Benutzeravatar
__blackjack__
User
Beiträge: 13122
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@McAce_2: Ich habe so gut wie nix verstanden. Was ist `x`? Im Text klingt das so als wäre das etwas in einem Gerät an einer Adresse, im Code ist das aber ein Name in Python.

Und sorry, aber bei:

Code: Alles auswählen

class ProtocolData:

    def get_parameter_list(self):


        parameter_list = [e for e in range(0,1550)]



        return parameter_list

# ...
        # An anderer Stelle im Modul
        parameter_list = ProtocolData().get_parameter_list()
Da muss ich einfach nach dem Sinn fragen beziehungsweise feststellen: das macht keinen Sinn. Das ist wenn man nur lesen zugreifen muss einfach ``parameters = range(1550)`` oder wenn darauf auch geschrieben wird ``parameters = list(range(1550))``. Die ”Klasse” ist einfach nur hochgradig unsinnig.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
McAce_2
User
Beiträge: 11
Registriert: Donnerstag 12. März 2020, 09:30

__blackjack__ hat geschrieben: Donnerstag 12. März 2020, 13:21 @McAce_2: Ich habe so gut wie nix verstanden. Was ist `x`? Im Text klingt das so als wäre das etwas in einem Gerät an einer Adresse, im Code ist das aber ein Name in Python.

Und sorry, aber bei:

Code: Alles auswählen

class ProtocolData:

    def get_parameter_list(self):


        parameter_list = [e for e in range(0,1550)]



        return parameter_list

# ...
        # An anderer Stelle im Modul
        parameter_list = ProtocolData().get_parameter_list()
Da muss ich einfach nach dem Sinn fragen beziehungsweise feststellen: das macht keinen Sinn. Das ist wenn man nur lesen zugreifen muss einfach ``parameters = range(1550)`` oder wenn darauf auch geschrieben wird ``parameters = list(range(1550))``. Die ”Klasse” ist einfach nur hochgradig unsinnig.
Natürlich ist die Klasse in dem Beispiel unsinnig, dort stehen die 1550 Parameter drin die verwendet werden, das macht aber kein Sinn die hier zu posten. Es wird eine Liste erzeugt die dem entspricht was ich hier verwende. nur auf das wesentliche reduziert. Nämlich eine Liste mit 1550 Elementen.

Ja ich weiß das wenn man nicht möchte das etwas überschrieben werden soll eine andere Variable verwendet werden soll, aber an der Sache kann ich hier nichts ändern, wenn dem so wäre hätte ich das Problem nicht.
Und genau das Problem würde ich gerne über die Abfrage der Adresse umgehen.
Benutzeravatar
__blackjack__
User
Beiträge: 13122
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@McAce_2: Auch wenn da 1550 Parameter erzeugt werden ist das als Klasse so unsinnig und wäre einfach nur eine Funktion. Klassen von denen ein Objekt erzeugt wird nur um dann eine Methode darauf aufzurufen und danach das Objekt wegzuwerfen sind komisch. Eine Klasse *mit* `__init__()` und nur einer Methode ist ja schon ein „code smell“, aber eine Klasse *ohne* `__init__()` und einer einzigen “Methode“ ist halt einfach nur eine Funktion. Das ist Python, da muss man nicht zwanghaft alles in Klassen stopfen. Und falls die tatsächliche Klasse noch wieder anders aussieht und die Methode auch tatsächlich eine Methode ist: wir können halt nur von dem ausgehen was Du hier zeigst.

Ich verstehe immer noch nicht welches Problem Du da eigentlich zu lösen versuchst. Schreib doch mal ein kleines Beispiel das genau das Problem illustriert, lauffägig ist, und ohne `pymodbus` & Co auskommt. Also etwas das jeder hier problemlos einfach mal ausführen kann.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
sparrow
User
Beiträge: 4197
Registriert: Freitag 17. April 2009, 10:28

Ich würde sogar sagen eine Funktion, die nur den Sinn hat eine konstante Liste von Elementen zurückzugeben und sonst nichts tut ist überflüssig.

Also bleibt:

Code: Alles auswählen

PARAMETERS = [e for e in range(0,1550)]
Das mag ich. Das ist übersichtlich.
McAce_2
User
Beiträge: 11
Registriert: Donnerstag 12. März 2020, 09:30

Ja ich weiß das die Klasse komisch aussieht, sie hat ihren Zweck hier erfüllt. ;-)
Das ganze Konstrukt ist wesentlich komplizierter, und ändert nichts an dem Problem außer das die Sache unübersichtlich gewesen wäre und es hätte nichts an dem Problem geändert, weshalb ich darauf verzichtet habe das hier zu posten.

Aber gut das du darauf hinweist das das kein guter Python Code ist, das ist er so in der Tat wirklich nicht. :-)

Ich habe das Problem gelöst, falls es jemand interessiert.

Code: Alles auswählen

x =  server_context.getValues(3, 16, 2)
server_context.setValues(3, 1060, server_context.getValues(3, 16, 2))
so setze ich den Bereich in 1060 mit dem Wert der in 16 steht.
Jetzt wird x immer noch zweimal beschrieben aber mit demselben Wert, und mehr wollte ich nicht, bzw. auf mehr habe ich keinen Einfluss, da der Rest halt gegeben ist und nicht abgeändert werden kann, leider.
Sirius3
User
Beiträge: 17758
Registriert: Sonntag 21. Oktober 2012, 17:20

Ich kann beim besten Willen nicht Deine „Lösung” und mit Deiner Problembeschreibung zusammen bekommen.

Was hat denn die ganze Klasse mit dem simplen übergeben eines Wertes in eine Funktion zu tun?

Code: Alles auswählen

x =  server_context.getValues(3, 16, 2)
server_context.setValues(3, 1060, x)
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich denke hier herrscht Verwirrung weil Modbus als eher spezielles Protokoll im Spiel ist. Da scheinen die Clients als Register-Raum dargestellt zu sein, aus dem man eben ab einer bestimmten Adresse eine Menge an Werten liest oder schreibt. Und ich vermute mal stark unser TE moechte an der Stelle einhaken, und die Daten erzeugn, statt das einfach auf eine Liste oder so abzubilden. Darum habe ich auch nach der Server-Version gefragt. Damit man sehen kann, wie ein solches Request konkret reinkommt, und dann verarbeitet wird.

Ich denke der Ansatz ist das Erstellen eines eigenen Block Storages: https://github.com/riptideio/pymodbus/b ... ore.py#L62

Den wuerde ich ableiten und einhaengen, mit prints/debugger statements versehen, um die callbacks zu verstehen.
McAce_2
User
Beiträge: 11
Registriert: Donnerstag 12. März 2020, 09:30

Die Klasse hat nichts mit dem Problem zu tun, ich weiß nicht warum das thematisiert worden ist, wahrscheinlich weil das eine schlecht gekürzte Version ist, die nicht dem Python Standard entspricht.


Mein Problem war das eine Variable x = server_context.getValues(3, 16, 2) gesetzt wurde und die Variable x wurde auch durch x = server_context.getValues(3, 1060, 2) gesetzt.
Nur das unter bestimmten Bedingen x = server_context.getValues(3, 16, 2) mit einem Validen Wert gesetzt wird und danach mit einem nicht validen Wert durch x = server_context.getValues(3, 1060, 2) gesetzt wird.

Weil x =server_context.getValues(3, 16, 2) zuerst gesetzt wird und im Anschluß x=server_context.getValues(3, 106,0 2) gesetzt wird ist in x ein nicht valider Wert gespeichert worden.
Das Problem wollte ich durch eine Abfrage der Adresse 1060 umgehen, was aber nicht zu funktionieren scheint. Jetzt habe ich das Problem so gelöst, dass wenn x=server_context.getValues(3, 16, 2) gesetzt wird, setze ich server_context.setValues(3, 1060, x) .

So wird x immer noch überschrieben, aber jetzt mit einem validen Wert.
McAce_2
User
Beiträge: 11
Registriert: Donnerstag 12. März 2020, 09:30

__deets__ hat geschrieben: Donnerstag 12. März 2020, 14:17 Ich denke hier herrscht Verwirrung weil Modbus als eher spezielles Protokoll im Spiel ist. Da scheinen die Clients als Register-Raum dargestellt zu sein, aus dem man eben ab einer bestimmten Adresse eine Menge an Werten liest oder schreibt. Und ich vermute mal stark unser TE moechte an der Stelle einhaken, und die Daten erzeugn, statt das einfach auf eine Liste oder so abzubilden. Darum habe ich auch nach der Server-Version gefragt. Damit man sehen kann, wie ein solches Request konkret reinkommt, und dann verarbeitet wird.

Ich denke der Ansatz ist das Erstellen eines eigenen Block Storages: https://github.com/riptideio/pymodbus/b ... ore.py#L62

Den wuerde ich ableiten und einhaengen, mit prints/debugger statements versehen, um die callbacks zu verstehen.
Genau, das ist das Problem gewesen und deswegen nur die Liste in einer Klasse. Das mit dem Ableiten hatte ich mir für den absoluten Worst Case aufgehoben.

Ich habe es jetzt anders gelöst, und das passt so.

x = server_context.getValues(3, 16, 2)
server_context.setValues(3, 1060, x)

Vielen Dank für die Hilfe. :-)
Sirius3
User
Beiträge: 17758
Registriert: Sonntag 21. Oktober 2012, 17:20

Ich weiß wirklich nicht, wie ModBus hier hereinspielt, aber ein

Code: Alles auswählen

x = server_context.getValues(3, 16, 2)
irgendein_anderer_name_so_dass_x_nicht_ueberschrieben_wird = server_context.getValues(3, 1060, 2)
sollte das Problem doch auch lösen.
McAce_2
User
Beiträge: 11
Registriert: Donnerstag 12. März 2020, 09:30

Ja das stimmt aber nicht bei meinem Problem, weil x unter zwei Bedingungen geschrieben wird wenn ich dann ein x_1 udn x_2 verwende weiß die nachgeschaltete Software nicht welchen Wert diese zu nehmen hat. Das Problem ist komplexer als ich nehme einfach eine zweite Variable.
Antworten