pyModbus

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
KolbenKeule
User
Beiträge: 9
Registriert: Freitag 31. März 2023, 13:20

Hi Leute,

ich habe ein ungewöhnliches Problem. Ich versuche mittels pyModBus Bibliothek ein Modbus Gateway über Netzwerk anzusteuern. An dem Gateway sind 16 Aktoren angeklemmt die einzeln angesteuert werden können. Die 16 Zustände der Aktoren werden in einem Register gespeichert. Das Register kann ich problemlos auslesen und neu beschreiben. Das klappt also alles einwandfrei. Mein Problem ist nun, dass ich nur eines der 16 Bits schalten möchte bzw ändern möchte ohne das die Zustände der anderen geändert werden. Hat das schonmal jemand gemacht und könnte mir einen Tipp geben? Bei google bin ich leider nur auf ein Bespiel gestoßen das mich aber nicht wirklich weitergebracht hat.

ein großes Danke schonmal im voraus.

Code: Alles auswählen

from pymodbus.client import ModbusTcpClient as ModbusClient
from pymodbus.constants import Endian
from pymodbus.payload import BinaryPayloadDecoder
from pymodbus.payload import BinaryPayloadBuilder
import pymodbus
import time



host = '192.168.1.10'
port = 502
client = ModbusClient(host, port)
client.connect()
print("Client verbunden!")

try:
    while True:
        
        builder = BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Little)
        builder.add_bits([1,0,0,0,0,0,0,0])        #Modbus 9-16
        builder.add_bits([0,0,0,0,0,0,0,0])        #Modbus 1-8
        payload = builder.build()
        client.write_registers(9000, payload, skip_encode=True, unit=1)
        #bg02 = client.read_input_registers(8000,16,unit=1)
        
        register = client.mask_write_register(9000, and_mask=0xFFFF, or_mask=0b1000000000000000, skip_encode=True)
        result = client.read_holding_registers(9000, 1, unit=1)
        decoder = BinaryPayloadDecoder.fromRegisters(result.registers, byteorder=Endian.Big, wordorder=Endian.Little) #Modbus Output 9-16
        sec_8_bits_in_reg = decoder.decode_bits()
        first_8_bits_in_reg = decoder.decode_bits()

        
        time.sleep(2)
        print("-" * 60)
        print("-" * 60)
        print(payload)
        print(result)
        print(first_8_bits_in_reg + sec_8_bits_in_reg)
        print("-" * 60)
        print("_" * 60)
        print("-" * 60)
        
except:
    client.write_registers(9000,0,unit=1) 
    client.close()
Benutzeravatar
__blackjack__
User
Beiträge: 13122
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@KolbenKeule: `pymodbus` wird importiert, aber nirgends verwendet. Ebenso wird `register` nirgends verwendet.

Warum teilst Du `add_bits()` auf zwei Aufrufe auf? Man muss ja deutlich weniger darüber nachdenken welcher Aufruf welchen Teil der 16 Bits hinzufügt, wenn man das einfach in einem Aufruf mit *einer* Liste erledigt.

`mask_write_register()` sollte noch mal den gleichen Wert schreiben wie das `write_register()` davor‽

Default-Argumente sind dazu da, das man sie nicht extra angeben muss. Wenn man nur Bits setzen will, macht `and_mask` anzugeben keinen Sinn.

Das `sleep()` steht an einer komischen Stelle.

Die Fehlerbehandlung ist keine. Und sollte wohl auch keine sein. Hier wurde offenbar ``except`` mit ``finally`` verwechselt. Und das `close()` sollte man durch eine Verwendung von ``with`` ersetzen.

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
import time

from pymodbus.client import ModbusTcpClient as ModbusClient
from pymodbus.constants import Endian
from pymodbus.payload import BinaryPayloadBuilder, BinaryPayloadDecoder

HOST = "192.168.1.10"
PORT = 502


def main():
    with ModbusClient(HOST, PORT) as client:
        client.connect()
        print("Client verbunden!")
        try:
            while True:
                builder = BinaryPayloadBuilder(
                    byteorder=Endian.Big, wordorder=Endian.Little
                )
                #
                # Modbus 1-16
                #
                builder.add_bits(
                    [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
                )
                payload = builder.build()
                client.write_registers(9000, payload, skip_encode=True, unit=1)

                client.mask_write_register(
                    9000, or_mask=0b1000_0000_0000_0000, skip_encode=True
                )

                response = client.read_holding_registers(9000, 1, unit=1)

                decoder = BinaryPayloadDecoder.fromRegisters(
                    response.registers,
                    byteorder=Endian.Big,
                    wordorder=Endian.Little,
                )
                low_bits = decoder.decode_bits()
                bits = decoder.decode_bits() + low_bits

                print("-" * 60)
                print(payload)
                print(response)
                print(bits)
                print("-" * 60)

                time.sleep(2)

        finally:
            client.write_registers(9000, 0, unit=1)


if __name__ == "__main__":
    main()
Falls das Programm die Quelle der Wahrheit bezüglich dieser Bits ist, dann kannst Du doch einfach immer die 16 Bit schreiben. Und Ändern ist Zuweisung eines Wertes in einer Liste an einem Index. Das ist ja kein Problem. Falls doch, müsstest Du (noch?) mal ein Grundlagentutorial durcharbeiten. In der Python-Dokumentation gibt es so etwas beispielsweise.

Wenn Du wirklich geziehlt einzelne Bits verändern willst, geht das wohl mit `mask_write_register()`.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
KolbenKeule
User
Beiträge: 9
Registriert: Freitag 31. März 2023, 13:20

@__blackjack__ vielen Dank für deine schnelle Antwort!
Du hast mich natürlich kalt erwischt^^ Denn Code(wenn man das so nennen kann) habe ich nur zum Testen verfasst damit mein Hauptcode nichts abbekommt und ich das Problem eingrenzen kann.
Mit dem Aufruf hast du natürlich recht, das ist mir gar nicht sofort aufgefallen;)

das mask_write_register soll ein Bit im Register ändern ohne die restlichen zu beeinflussen. Dafür setze ich am Anfang ein Bit (mit dem eine Lampe angeht) und versuche danach mit dem Befehl ein zusätzliches Bit an einer anderen Stelle zu schreiben, ohne das das erste (oder die anderen) geändert werden.

Soweit ich verstanden habe ist and_mask dafür da alle Bits auf 0 und mit dem Ausdruck wird das ignoriert.

Ich habe den Code getestet aber leider besteht das gleiche Ergebnis. Ich schreibe das eine Bit, die Lampe geht an aber ich kann das Register danach nicht bearbeiten bzw ein einzelnes Bit setzen.

P.S.: was meinst du mit einer Liste?

Beste Grüße und vielen Dank:)
KolbenKeule
User
Beiträge: 9
Registriert: Freitag 31. März 2023, 13:20

__blackjack__ Ich glaube auch fast die Adressierung stimmt nicht aber wie kann es dann sein das ich das Register mit der Adresse und einem anderen Befehl beschreiben kann?
Benutzeravatar
__blackjack__
User
Beiträge: 13122
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@KolbenKeule: Das sieht aber so aus als würde mit dem `write_mask_register()` das gleiche Bit wie bei dem `write_register()` davor gesetzt werden soll. Ja, die `and_mask` von 0xFFFF verändert nichts am Ergebnis, darum ist das auch der Defaultwert und muss nicht angegeben werden.

Natürlich sollte der Code das selbe Ergebnis haben, ich habe da ja inhaltlich nichts geändert. Ich vermute meine Vermutung stimmt und Du machst mit dem ersten Schreibbefehl die Lampe an. Und mit dem anderen auch. Da die bereits an ist, ändert sich natürlich nichts. Ansonsten müsstest Du mal sagen warum Du da konkret ein anderes Ergebnis erwartest wenn Du effektiv zweimal das gleiche Bit setzt.

Das mit den `payload`-Klassen ist alles ein bisschen umständlich, das würde ich mir sparen und einfach mit 16-Bit-Ganzzahlen arbeiten. Wichtig ist hier einfach bitweise Verknüpfungen zu verstehen. Also „und“ (``&``), „oder“ (``|``), Negation (``~``), und den Zusammenhang zwischen 2er-Potenzen und Bits und da dann vielleicht auch noch die bitweise Verschiebung (``<<`` (und ``>>``)): https://de.wikipedia.org/wiki/Bitweiser_Operator

Wenn man das einfach als 16-Bit-Ganzzahl sieht, mit den Bits 0 bis 15 (von rechts nach Links gezählt wenn man das als Binärzahl schreibt), welches ist dann die Lampe, und welches soll testweise zusätzlich zur Lampe gesetzt werden? Wenn Du den ganzen `payload`-Kram da weg lässt und einfach ``0b1000_0000_0000_0000`` in das Register schreibst, geht dann die Lampe an?
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
KolbenKeule
User
Beiträge: 9
Registriert: Freitag 31. März 2023, 13:20

@__blackjack__: Ok, dann kann ich den Parameter der 'and_mask' weglassen. Das erste Bit welches ich setze soll das an der 9. Stelle sein (was auch super funktioniert). Das Bit welches ich im Register ändern möchte sollte eigentlich das erste sein und damit soll dann ein Magnetventil geschaltet werden.

Das kann natürlich sein, da ich schonmal Probleme mit der Reihenfolge der 2 Bytes im Register hatte. Ich denke mir auch, dass es ein wenig umständlich mit dem payload und der Kodierung/Dekodierung ist, nur hatte ich bis dahin noch keine bessere Lösung gefunden. Vielen Dank für den Link, ich werde mir das mal anschauen und mich in den Bereich einarbeiten.

Die Lampe hängt auf dem ModBusgateway an 9. Stelle und das Ventil an der ersten. Also wenn die Zahl mit, ich glaube Little-Endian, genommen wird dann ist die Lampe an erste Stelle und du hast mit deiner Vermutung recht^^ Somit wird der Ausgang zweimal geschaltet und damit ist auch wahrscheinlich klar warum nie eine Fehlermeldung im Code kam.

Ich werde das Mo testen und werde dir dann Feedback geben ob alles geklappt hat.

Vielen Dank für deine Hilfe, du hast mir echt weitergeholfen :)
Antworten