Hallo Zusammen,
Ich lese meinen Stromzähler über dessen RS485 Schnittstelle aus. Als
Antwort bekomme ich Zeichenketten wie diese:
1
b'1-0:1.8.0*255(002131.9294*kWh)\r\n'
In den Klammern steht der Zählerstand, den es jetzt rauszuschneiden
gilt.
Mit meinem Code kann ich die beiden Klammern erkennen und rausschneiden:
1
002131.9294*kWh
Wie man sieht kann ich den String zwischen den Klammern rausschneiden.
Leider scheitere ich nun dran, die Postition des *-chens zu finden. Habt
ihr eine Idee, wie ich in Python das Sternchen erkennen, und dann den
verliebenen String 002131.9294 zu einer float überführen kann.
Hinweis, die Anzahl der Stellen ist für die 24 Speicherzeillen ist immer
anders. Lediglich die Klammern sowie das Sternchen kommt immer vor.
Grüße
Andreas
Ich vermute, dass du die Zahl mit einem regulären Ausdruck bereits
extrahierst, oder?
Dem Stern kannst du ganz einfach seine Sonderfunktion nehmen, indem du
ihn in eckige Klammern fasst (character class).
Also ungefähr so:
1
>>> import re
2
>>> x = re.compile('.*[(](.*)[*](.*)[)].*')
3
>>> m = x.match('1-0:1.8.0*255(002131.9294*kWh)')
4
>>> m.group(1)
5
'002131.9294'
6
>>> m.group(2)
7
'kWh'
ps: Ja, ich weiß, reguläre Ausdrücke sind write-only. :-)
und wie es dort zur "Gruppenbildung" kommt.
Macht re.compile je eine Gruppe
1. mit den Zeichen vor "("
2. mit den Zeichen zwischen "(" und "."
3. mit den Zeichen zwischen "." und "*"
4. mit den Zeichen zwischen "*" und ")"
5. mit den Zeichen ab ")"
P.S.:
Bis jetzt kann ich folgendes zusammenschneiden:
Andreas G. schrieb:> Macht re.compile je eine Gruppe
Sag ich doch, die sind write-only. ;-)
Gruppen werden mit '(' und ')' gebildet. Sie werden bei 1 beginnend
nummeriert (die Gruppe 0 ist reserviert für den "full match", also
alles, was überhaupt gepasst hat).
Die '[' ']' umklammern eine character class. Das kann beispielsweise
ein Folge sein wie '[A-Za-z]' für alle ASCII-Buchstaben oder '[0-3]' für
die ersten vier Ziffern. In diesem Falle sind die character classes alle
jeweils nur mit einem Element angelegt, der einzige Sinn dahinter ist
es, die Sonderfunktion von ()* aufzuheben, damit man sie später selbst
in den Vergleich einbeziehen kann. (Das geht auch mit Backslash, aber je
nach Tool muss man dann wieder gucken, ob man einen oder zwei
Backslashes schreiben muss, um tatsächlich im RE einen Backslash zu
bekommen. Die character class funktioniert immer.)
.* wiederum passt auf beliebige Teil-Strings dazwischen. Bei
Mehrdeutigkeiten würde jeweils der längstmögliche damit gewählt, aber da
die Begrenzungen mit (…*…) hier eindeutig sind, hat man damit kein
Problem.
ps: Mein RE passt nur auf die Strings von dir, die in den Klammern
"Maßzahl*Maßeinheit" haben. Wenn du die anderen auch haben willst, würde
ich zweistufig rangehen: zuerst den Inhalt des Klammernpaares ermitteln,
danach dann das mit dem Sternchen. Da musst du beim x.match allerdings
das Ergebnis auswerten, denn das ergibt dann keinen Match für die
Strings, die kein Sternchen haben.
Oder du testest auf das Sternchen vorab mit .index():
1
>>> s = 'foo*bar'
2
>>> t = 'mumble'
3
>>> s.index('*')
4
3
5
>>> t.index('*')
6
Traceback (most recent call last):
7
File "<stdin>", line 1, in <module>
8
ValueError: substring not found
Musst du halt den ValueError abfangen um zu wissen, dass an dieser
Stelle kein Sternchen da ist.
In dem Falle lohnt der zweite RE aber auch gar nicht, wenn es keinen
ValueError gab, kannst du ja die beiden Teil-Strings gleich mit Slices
extrahieren ([0:sternpos] bzw. [sternpos+1:]).
Andreas G. schrieb:> Deinen Code verstehe ich nicht ganz:>>> x => re.compile('.*[(](.*)[*](.*)[)].*')>> und wie es dort zur "Gruppenbildung" kommt.
ich finde den match unnötig Kompliziert wie wäre es mit
'\((?P<float>\d+\.\d+)\*kWh\)' das braucht nur 12 Steps laut
https://regex101.com/ gegen die 41 von dem Orginal Ansatz, ausserdem
vermute ich das, dass ganze .* matchen, vermutlich zum catastrophic
backtracking neigen kann.
1
def current2float(rs485: bytes):
2
match = re.search(r'\((?P<float>\d+\.\d+)\*kWh\)',
Seit ich mal gute PEG-Bibliotheken genutzt habe*, würde ich Regex nie
mehr zum Parsen verwenden.
PEG ist deutlich mächtiger, wartbarer und zugleich einfacher(!) zu
formulieren und man erhält die garantierte Erkennung syntaktisch
kaputter Eingaben auch noch dazu.
*keine Ahnung bei Python
Klaus schrieb:> Ein einfaches Arbeiten mit pos = text.find("*") funktioniert bei dir> nicht?
Ne. Das Sternchen ist wohl ein Sonder/Steuerzeichen. Da wir nix
gefunden.
Das zerlegt den String in Teile die dann in Liste liegen.
re.split('(|)', string) sagt, dass string geteilt werden soll und zwar
sowohl bei ( als auch bei ). Liste hat also am Ende 3 Einträge, der
Mittlere, Liste[1] enthält dann 002131.9294*kWh.
Imonbln schrieb:> Andreas G. schrieb:>> Deinen Code verstehe ich nicht ganz:>>> x =>> re.compile('.*[(](.*)[*](.*)[)].*')>>>> und wie es dort zur "Gruppenbildung" kommt.>> ich finde den match unnötig Kompliziert wie wäre es mit>> '\((?P<float>\d+\.\d+)\*kWh\)' das braucht nur 12 Steps laut> https://regex101.com/ gegen die 41 von dem Orginal Ansatz, ausserdem> vermute ich das, dass ganze .* matchen, vermutlich zum catastrophic> backtracking neigen kann.>>
1
> def current2float(rs485: bytes):
2
> match = re.search(r'\((?P<float>\d+\.\d+)\*kWh\)',
>> Sollte die kWh als float wiedergeben oder eine Exception Schmeißen.
Bzg. Abfangen von exeptions: die Ausgabe ist i.d.R. Immer die selbe, da
ich nur ein und den selben Stromzähler auslese.
Deinen vorschlag probiere ich nacha mal aus.
Andreas G. schrieb:> Ne. Das Sternchen ist wohl ein Sonder/Steuerzeichen. Da wir nix> gefunden.
Wenn er es als string einlesen würde und nicht binär, würde es
funktionieren:
> In dem Falle lohnt der zweite RE aber auch gar nicht, wenn es keinen> ValueError gab, kannst du ja die beiden Teil-Strings gleich mit Slices> extrahieren ([0:sternpos] bzw. [sternpos+1:]).
Das wäre wohl das Leichteste. I'll give it a try.
Ja, man könnte das Pattern größer wählen, damit das ganze robuster wird.
Ja, auch sonst könnte man vielleicht noch ein paar Gedanken
verschwenden, wie man das ganze abhärten könnte.
Ja, man sollte die letzte Zeile unbedingt in ein try ... except
verpacken.
Ja, der TE sollte sich mal in reguläre Ausdrücke einlesen, allgemein und
in Python.
Davon abgesehen bin ich schon immer wieder überrascht, wie kompliziert
es manchmal sein muss ...
>> Ja, man könnte das Pattern größer wählen, damit das ganze robuster wird.> Ja, auch sonst könnte man vielleicht noch ein paar Gedanken> verschwenden, wie man das ganze abhärten könnte.> Ja, man sollte die letzte Zeile unbedingt in ein try ... except> verpacken.> Ja, der TE sollte sich mal in reguläre Ausdrücke einlesen, allgemein und> in Python.>> Davon abgesehen bin ich schon immer wieder überrascht, wie kompliziert> es manchmal sein muss ...
Ach Heiner, ich sollte so viel, Vorallem abnehmen und Bier kaufen 😜
Andreas G. schrieb:> Ach Heiner, ich sollte so viel, Vorallem abnehmen und Bier kaufen 😜
Dito. RegEx lernen läge in meinen Prioritäten unter Bier und noch über
Abnehmen.
Cheers.
Also Pythons re und davon vorallem named groups haben es mir schon
angetan.
Damit bekommt man ein dict wo die Namen tacheles reden und muss nicht
indirekt über Indizes sich verzählen.
In den Beispielen dazu (tutorial?) sind auch kommentierte REs
aufgeführt, sodass diese nicht writeonly bleiben müssen.
Pro-Tipp1: mehrzeilig schreiben!
Pro-Tipp2: REs in Python immer als rawstring schreiben.
1
re.compile('.*[(](.*)[*](.*)[)].*')
...finde ich stümperhaft, jedoch bei REs führen letztlich viele Wege ins
ROM ;-)
Der Ansatz ist doch hier:
-a: vom Anfang bis & inkl. erste Klammer '(' ignorieren;
-b: dann bis '*' als Wert nehmen (Ziffern u. Dezimalpunkt);
-c: Stern '*' ignorieren;
-d: dann bis vor ')' als Einheit nehmen;
-e: ')' und Rest bis Ende ignorieren;
Also, in erstem Anlauf:
-a: r'^[^)]+\('
-b: r'[\d\.]+'
-c: r'\*'
-d: r'[^\)]+'
-e: r'\).*$'
Im 2ten Durchläuft benennen wir die Gruppen Wert und Einheit :
-a: r'^[^)]+\('
-b: r'(?P<Wert>' r'[\d\.]+' r')'
-c: r'\*'
-d: r'(?P<Einheit>' r'[^\)]+' r')'
-e: r'\).*$'
Das verbleibende ist nur noch Fingerübung...
Andreas G. schrieb:> Ich lese meinen Stromzähler über dessen RS485 Schnittstelle aus. Als> Antwort bekomme ich Zeichenketten wie diese:>
1
>b'1-0:1.8.0*255(002131.9294*kWh)\r\n'
2
>
> [...]> Wie man sieht kann ich den String zwischen den Klammern rausschneiden.> Leider scheitere ich nun dran, die Postition des *-chens zu finden. Habt> ihr eine Idee, wie ich in Python das Sternchen erkennen, und dann den> verliebenen String 002131.9294 zu einer float überführen kann.
Äh, Regex?
Andreas G. schrieb:> Klaus schrieb:>> Ein einfaches Arbeiten mit pos = text.find("*") funktioniert bei dir>> nicht?>> Ne. Das Sternchen ist wohl ein Sonder/Steuerzeichen. Da wir nix> gefunden.
Ja. Weil "*" ein str-, Dein text aber ein bytes-Objekt ist. Mit
text.find(b"*") würde das natürlich funktionieren. Ist aber Unfug,
str.split() und bytes.split() reichen.