String auf bestimmte Zeichen prüfen

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
patrickk83
User
Beiträge: 7
Registriert: Montag 16. Januar 2023, 12:02

Hallo Leute!

Ich versuche gerade eine Waage mit einem Raspberry Pi auszuwerten. Die Waage verfügt über eine RS232 Schnittstelle und ist mit einem Kabel an einem USB zu RS232 Adapter angeschlossen. Um nun das aktuelle Gewicht auszugeben, muss man die Hardwarenummer (eingestellt 27 bzw. 0x1B) + das Zeichen "p" (0x70) über die serielle Schnittstelle senden. Das funktioniert auch problemlos. Der einfache Code dazu sieht so aus:

Code: Alles auswählen

import atexit
import serial
import time

ser = serial.Serial(port='/dev/ttyUSB0', baudrate=9600, timeout=0.1)
atexit.register(ser.close)

while True:
	ser.write(b'\x1B\x70')  # x1B -> 27    x70 -> p
	print(ser.readline())
	time.sleep(1)
Die Ausgabe sieht dabei wie folgt aus:

Code: Alles auswählen

b'   57.372 ct\r\n'
b'   57.372 ct\r\n'
b'   57.372 ct\r\n'
An der Ausgabe ist zu sehen, dass die Gewichtseinheit auf ct (Karat) eingestellt ist. Ziel ist es genau diese eingestellte Gewichtseinheit zu prüfen und auf die gewünschte Einheit GN (Grain) zu ändern. Um die Einheit zu wechseln muss wieder die Hardwarenummer (0x1B) sowie das Zeichen "s" (0x73) an die Waage gesendet werden. Funktioniert im Prinzip auch wenn ich es einzeln an die Waage sende.
Ich würde aber gerne eine Funktion einbauen um den übertragenen String auf die aktuell eingestellte Gewichtseinheit zu überprüfen und bei Abweichung solange den Code für das Ändern der Gewichtseinheit senden bis die gewünschte Einheit eingestellt ist.

Hier mal meine Überlegung:

Code: Alles auswählen

import atexit
import serial
import time

ser = serial.Serial(port='/dev/ttyUSB0', baudrate=9600, timeout=0.1)
atexit.register(ser.close)

def change_Units():
  ser.write(b'\x1B\x73')

def read_Units():
  ser.write(b'\x1B\x70')
  units = ser.readline()
  units = units[9:12]
  if units != " GN":
    change_Units()
    time.sleep(1)

  print(units)

if __name__ == "__main__":

  while True:

    read_Units()

Die Bedingung wird aber irgendwie nie erfüllt. Die Einheiten werden zwar geändert aber selbst wenn die gewünschten Grain eingestellt sind, läuft die Schleife weiter.

Hier noch ein kurzer Auszug aus dem Manual:

Bild
Bild

Wäre toll wenn mal jemand da drüber sehen würde.
Benutzeravatar
__blackjack__
User
Beiträge: 14067
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@patrickk83: Die Bedingung wird nie erfüllt weil `bytes` niemals gleich Zeichenketten (`str`) sind:

Code: Alles auswählen

In [229]: "Hallo" == b"Hallo"
Out[229]: False

In [230]: b"Hallo" == b"Hallo"
Out[230]: True
Eingerückt wird vier Leerzeichen pro Ebene, nicht zwei.

Das mit dem `atexit()` ist etwas exotisch. `Serial`-Objekte sind Kontextmanager, das würde man also mit der ``with``-Anweisung lösen.

Du hast ein `timeout` eingestellt, der Code kümmert sich aber überhaupt gar nicht um den Fall wenn der Fall mal eintreten sollte.

Das „slicen“ mit den magischen Indexzahlen ist nicht so wirklich leicht zu lesen.

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase). Die Funktion braucht das `Serial`-Objekt als Argument. Das darf nicht einfach so magisch aus der Umgebung kommen.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.

`units` wird erst an die ganze Zeile gebunden und nicht nur an die Einheit (Einzahl) — das stimmt dann ja inhaltlich nicht. Ebenso wie `read_Units()` deutlich mehr macht als die Einheit zu lesen. Entweder ist dort der Name falsch, oder der Code ist falsch zwischen den Funktionen aufgeteilt.

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
import time

import serial


def change_unit(connection):
    connection.write(b"\x1Bs")


def read_unit(connection):
    connection.write(b"\x1Bp")
    _, _, unit = connection.readline().rstrip().rpartition(b" ")
    return unit


def main():
    with serial.Serial("/dev/ttyUSB0") as connection:
        while True:
            unit = read_unit(connection)
            if unit == b"GN":
                change_unit(connection)
                time.sleep(1)
            print(unit)


if __name__ == "__main__":
    main()
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Sirius3
User
Beiträge: 18275
Registriert: Sonntag 21. Oktober 2012, 17:20

Schreiben die hier wirklich bits, wenn sie bytes meinen??

Bei Dir gilt das übrigens auch, Du schreibst " GN" was ein String ist, Du möchtest aber Bytes b" GN".
Eingerückt wird immer mit 4 Leerzeichen pro Ebene, nicht 2. Funktionen und Variablen werden klein geschrieben, globale Variablen darf man nicht nutzen. Man sollte with statt atexit verwenden.

Code: Alles auswählen

import serial
import time

def change_unit(port):
    port.write(b'\x1B\x73')

def read_unit(port):
    port.write(b'\x1B\x70')
    line = port.readline()
    units = line[9:12]
    if units != b" GN":
        change_units(port)
    return unit

def main():
    with serial.Serial(port='/dev/ttyUSB0', baudrate=9600, timeout=0.1) as port:
        print(read_unit(port))
        time.sleep(1)

if __name__ == "__main__":
    main()
patrickk83
User
Beiträge: 7
Registriert: Montag 16. Januar 2023, 12:02

Danke euch beiden für die raschen Antworten! Python ist komplettes Neuland für mich, ich komme aus der Ära C und dass ist jetzt auch schon ein paar Monde her ;-)
Werde eure sehr hilfreichen Tipps gleich mal austesten.
patrickk83
User
Beiträge: 7
Registriert: Montag 16. Januar 2023, 12:02

Noch eine Verständnisfrage: unit = line[9:12] -> Beginnt man bei Python bei 0 zu zählen? Also wären das jetzt die Bytes 9 bis 12 oder 10 bis 13?
Der Programmcode von Sirius3 wird allerdings nur einmal durchlaufen dann wird das Programm beendet... Die Einheit wird gewechselt dann beendet das Programm obwohl noch nicht die korrekte Einheit "GN" eingestellt ist.
Sirius3
User
Beiträge: 18275
Registriert: Sonntag 21. Oktober 2012, 17:20

Ja, Python beginnt bei 0. Wenn das Protokoll wirklich so ist, dass es fixe Byte-Positionen benutzt, kommt man um das Index-Auslesen fast nicht drumrum. Oder ein regulärer Ausdruck zum Parsen. Dazu müßte man aber noch Beispiele kennen, die negativ sind, oder mehr als 3 Vorkommastellen (oder 7 Zeichen) haben.
Und bei mir fehlt die while-Schleife.
patrickk83
User
Beiträge: 7
Registriert: Montag 16. Januar 2023, 12:02

Ja, das Protokoll verwendet fixe Byte-Positionen. Ein Beispiel mit negativem Wert kann ich gleich nachliefern. Ich bin auch schon verzweifelt am Überlegen wo eine while-Schleife am idealsten wäre... Wäre echt super wenn ich noch weiter auf Unterstützung bitten dürfte.
patrickk83
User
Beiträge: 7
Registriert: Montag 16. Januar 2023, 12:02

Für negative Werte sieht die Ausgabe so aus:

Code: Alles auswählen

b'-  176.47 GN\r\n'
Grundsätzlich möchte ich nur erreichen, dass eine Schleife solange den Code zum Einheitenwechseln sendet, bis diese drei Bytes " GN" enthalten (ich denke, vor dem GN steht noch ein Leerzeichen da einige Gewichtseinheiten dreistellig sein können).
Sirius3
User
Beiträge: 18275
Registriert: Sonntag 21. Oktober 2012, 17:20

Dann würde ich den Text mit einem regulären Ausdruck parsen:

Code: Alles auswählen

sign, value, unit = re.fullmatch(rb'(-)?\s*(\d*\.\d*)\s*(\S*)\s*', line).groups()
patrickk83
User
Beiträge: 7
Registriert: Montag 16. Januar 2023, 12:02

Danke, ich sehe mir das morgen genauer an.
patrickk83
User
Beiträge: 7
Registriert: Montag 16. Januar 2023, 12:02

Folgender Code schaltet die Einheiten meiner Waage solange durch, bis die gewünschte Einheit erreicht ist.

Code: Alles auswählen

import atexit
import serial
import time

ser = serial.Serial(port='/dev/ttyUSB0', baudrate=9600, timeout=0.1)
atexit.register(ser.close)

def change_units():
    ser.write(b'\x1B\x73')

def read_units():
    ser.write(b'\x1B\x70')
    units = ser.readline().decode('utf-8')
    units = units[9:12]
    print(units)
    return units


if __name__ == "__main__":

    while read_units() != " GN":
        change_units()
        time.sleep(1)
Antworten