Forum: PC-Programmierung Python: pySerial Lese-Fehler unter Win10


von Michael S. (rbs_phoenix)


Angehängte Dateien:

Lesenswert?

Hallo zusammen.
Ich möchte mit einem Python-Skript ein Bild von einem Scanner abholen. 
Dieser ist im CDC Modus, ist also per USB angeschlossen aber taucht als 
COM Port im Geräte-Manager auf.
Ich benutze pySerial, um ein Befehl zu senden und dann die Antwort zu 
lesen. Bei einem JPEG Bild im Verhältnis 1:16 (man kann im Befehl u.a. 
angeben, ob es 1:1, 1:4 oder 1:16 sein soll) funktioniert es 
einwandfrei. Den Header und die letzten 3 Byte, die das Ende der 
Übertragung angeben, werden weggeworfen und der Rest kommt direkt in 
eine name.jpg Datei geschrieben. Diese ist ca. 10kB groß.

Nun wollte ich das Bild im Verhältnis 1:1 abholen, doch das funktioniert 
nicht so recht. Mit der Software vom Scanner-Hersteller kann ich das 
Bild direkt abholen und anzeigen lassen. Außerdem hat es ein Terminal, 
mit dem ich den Befehl senden und die Antwort-Daten in HEX anzeigen 
lassen kann. Wenn ich die kopiere und in eine Datei schreibe, ist dort 
auch das Bild korrekt beim Anzeigen (siehe 1zu1_test_terminal.jpg).
In meinem Python Skript nutze ich die Funktion 
s.read_until(b'\x06\x3b\x03'), um das Ende der Übertragung abzuwarten. 
Das wird auch durchgehend erfolgreich erkannt. Allerdings ist das Bild 
(siehe 1zu1_test_python.jpg) korrupt. Der Grund liegt wohl daran, dass 
Pakete während der Übertragung irgendwie untergehen. Im Header der 
Antwort steht am Ende die Anzahl der Bytes der Nutzdaten die im 
Anschluss folgen. Diese ist 87835 (Byte). Wenn ich dann aber die Länge 
der gelesenen Antwort ermittle, schwankt diese bei jedem mal lesen (z.B. 
87006, 87198, 87134, 87070, 86942, ...). Daher ist es plausibel, dass 
Das Bild komisch aussieht. Außerdem fällt auf, dass zwischen den 
schwankenden "gelesene-Byte-Längen" immer ein Vielfaches von 64 liegt.

Nach etwas Recherche habe ich rausbekommen, dass es echt ein Problem in 
Kombination mit dem neuen Treiber (mit Win10 eingeführt) "usbser.sys" 
und dem pySerial Pakets zu sein scheint. Der letzte Stand von pySerial 
ist auch ca. 3 Jahre alt und ich bezweifle, dass daran noch aktiv 
gearbeitet wird.

Beim Googlen nach serieller Kommunikation mit Python wird pySerial aber 
in 90%+ der Fälle verwendet. Alternativ dazu gibt es ein WinUsbCDC 
Paket, welches in der Beschreibung direkt auf das oben genannte Phänomen 
hinweist:
https://pypi.org/project/WinUsbCDC/#description

Eine weitere Möglichkeit scheint pyUSB zu sein. Beide Pakete nutzen den 
libusb-1.0 Treiber.

Aus mehreren Gründen würde ich die Pakete aber nicht nutzen wollen:
1. Die Verbindung scheint über pID und vID zu laufen, doch es können 
auch mehrere gleiche Scanner am PC angeschlossen sein, wodurch eine 
Unterscheidung darüber nicht möglich wäre.
2. Man muss den Treiber des Port aktiv wechseln. Ich bin mir nicht 
sicher, ob das an die pID und vID geknüpft ist und es egal ist, wo und 
wie viele Scanner angeschlossen sind, aber es ist ein händischer 
Vorgang, den ich gerne umgehen würde (das Skript soll auf mehreren PCs 
laufen und dort müsste es überall manuell geändert werden).
3. Die Software des Herstellers funktioniert bei umgestelltem Treiber 
nicht mehr bzw. findet den Scanner erst gar nicht, um sich mit ihm dann 
verbinden zu können. Ohne ein Wechsel des Treibers für die Schnittstelle 
geht also entweder die Herstellersoftware ODER das Python-Script.

*Um nun endlich auf den Punkt zu kommen:*

Kennt jemand das Problem mit pySerial und Windows 10 und weiß von einem 
Workaround? Oder gibt es ein anderes Paket, das den usbser.sys Treiber 
nutzt? Ich habe dazu nirgends was gefunden. Da die Software vom 
Hersteller offenbar auch den usbser.sys Treiber nutzt, ist dort 
anscheinend kein Fehler per se drin. Sonst müsste die Software die Daten 
ja auch nicht korrekt lesen. Den Berichten zu folge taucht das Problem 
mit pySerial aber z.B. unter Win7 nicht auf. Ich bin aber an Win10 
gebunden.

Hat jemand eine Idee oder eine Alternative? Ist es z.B. möglich, die 
usbser.sys in Python einzubinden? Ich würde allerdings auch ungern 
pySerial ungern neu schreiben müssen. Doch ich kann mir nicht 
vorstellen, dass seit Win10 da niemand an einer Lösung gearbeitet hat, 
die weiterhin den "Standard-Treiber" nutzt.

Vielen Dank schonmal
Grüße
Michael

von Christian R. (supachris)


Lesenswert?

64 Byte Stücke Versatz/Verlust klingt nach einem suboptimalen Setting im 
Treiber des USB Serial für die Buffer Größe. Bei den FTDI kann man das 
einstellen.
Was auch sein kann: Hat das Ding eventuell (Hardware) Handshake, und du 
benutzt ihn nicht? So dass der Empfänger im PC mit Daten überfahren 
wird, die er nicht schnell genug los wird...

von Michael S. (rbs_phoenix)


Lesenswert?

Also ich habe zum Einen schonmal den Eingangsbuffer auf ca. 300000 
gesetzt, sodass der nicht volllaufen kann. Ich habe auch beim öffnen des 
Ports jede mögliche Kombination aus xonxoff, dsrdtr und rtscts 
eingestellt. Bisher hat das alles nichts geholfen.

Ich weiß leider nicht, was direkt für ein Chip darin ist. Ich schätze 
mal ein ASIC, aber bin mir nicht sicher. So oder so schafft es die 
Hersteller-Software ja mit dem Treiber. Ich habe auch von einem Fall 
gelesen, wo der selbe Fehler auftrat aber es mit einem externen 
Terminal-Tool a la Putty keine Probleme gab.

Daher meine starke Vermutung, dass es an einer Kombination aus Treiber 
und pySerial liegt. Nur schätze ich, dass die allermeisten keinen so 
hohen Datendurchsatz/Datenmenge haben. Und wenn können denke ich mal die 
meisten bei einer Einzel/-Bastellösung auch auf einen anderen Treiber 
und pyUSB oder WinUsbCDC etc. umsteigen.

von Christian R. (supachris)


Lesenswert?

Probier doch mal PythonNet und nutze dann die .NET Klasse für den COM 
Port. Vielleicht geht das effizienter. Manchmal ist Python sehr seltsam.

von Sebastian W. (wangnick)


Lesenswert?

Sicher dass es nicht was trivialeres ist, z.B. universal newline oder 
irgend ein encoding/decoding, was dir die Daten zerschießt?

LG, Sebastian

von Michael S. (rbs_phoenix)


Lesenswert?

Christian R. schrieb:
> Probier doch mal PythonNet und nutze dann die .NET Klasse für den
> COM
> Port. Vielleicht geht das effizienter. Manchmal ist Python sehr seltsam.

Moin, ich melde mich nun mal zurück.

Ich habe es mit pythonnet mal probiert. Es klappt sogar ganz gut, 
nachdem ich ein paar Startschwierigkeiten speziell mit den Enums hatte. 
Aber EIGENTLICH klappt es jetzt. Ich schicke einen String hin, nutze den 
Befehl ReadTo() um auf meine Dreierfolge am Ende der Übertragung zu 
triggern und habe dann exakt so viele Bytes Nutzdaten wie mir der Header 
sagt.

Das einzige, was ich jetzt noch hinbekommen muss ist die richtige 
Formatierung der Antwort.
1
result = s.ReadTo(b'\x06\x3b\x03'.decode())
2
time.sleep(0.1)
3
s.Close()
4
#result = result.encode('utf-8')
5
6
print("Header:")
7
print(result[:29])

Dies ist ein Auszug vom Python-Skript.

So gibt er mir die Fehlermeldung:
1
TypeError: can't concat str to bytes

Wenn ich den resultstring in utf-8 encode, steht dort
1
Header:
2
b'\x02\x010000@IMGGET0T0R2F2Q00018B03'

Was erstmal richtig ist. Gucke ich mir aber die z.B. ersten 34 Zeichen 
an, steht da:
1
b'\x02\x010000@IMGGET0T0R2F2Q00018B03????\x00
Obwohl es eigentlich so sein müsste:
1
b'\x02\x010000@IMGGET0T0R2F2Q00018B03\xff\xd8\xff\xe0\x00

Er ersetzt also alle Zeichen >127 / >0x7F durch Fragezeichen.

Das Problem werde ich nun versuchen zu finden. Wenn du aber erkennst, wo 
mein Fehler liegt, wäre ich dir sehr dankbar, mich darauf hinzuweisen.

Vielen Dank

Michael

: Bearbeitet durch User
von Michael S. (rbs_phoenix)


Lesenswert?

Ich schätze, dass es daran liegt:
Für System.IO.Ports.SerialPort gibt es eine Eigenschaft Encoding.
1
Ein Encoding-Objekt. Der Standardwert ist ASCIIEncoding.

Wenn ich nun das auf "UTF8Encoding" setze, läuft er Ewigkeiten (vmtl 
weil die ReadTo-Bedingung nicht mehr passt) und ich bekomme als erste 
Zeichen das zurück:
1
b'\x02\x010000@IMGGET0T0R2F2Q00018B03\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd\x00
Die vorherigen vier ? sind also durch \xef\xbf\bd ersetzt - und das 
ganze 4 mal hintereinander. Ist ja aber falsch, weil es eigentlich drei 
unterschiedliche Bytes sein müssten (2x 0xff, einmal 0xd8 und einmal 
0xe0)

Wenn ich Encoding auf "Encoding.GetEncoding(1252)" stelle, dann kommt 
immerhin das zurück:
1
b'\x02\x010000@IMGGET0T0R2F2Q00018B03\xc3\xbf\xc3\x98\xc3\xbf\xc3\xa0\x00

Und 0xc3 0xbf scheint utf8 für 0xff zu sein. Jetzt muss ich noch 
irgendwie die Daten kürzen

von Dirk B. (dirkb2)


Lesenswert?

Wie wäre es mit Binary-Encodig?
Also keine Umwandlung.

Wenn bei UTF-8 Das Bit-7 gesetzt ist, muss eine gewisse Reihenfolge 
bzgl. Bit 6 und 7 eingehalten werden.
Wenn die nicht stimmt, gibt es Datensalat.

von Dirk B. (dirkb2)


Lesenswert?

Michael S. schrieb:
> Und 0xc3 0xbf scheint utf8 für 0xff zu sein. Jetzt muss ich noch
> irgendwie die Daten kürzen

Dafür gibt es Regeln. Du musst da nicht raten.

von Norbert (der_norbert)


Lesenswert?

Ich vermute das du dich da etwas verrannt hast.
Die Bytes in einem Bytearray können entweder korrekte Daten für ein 
beliebiges Encoding beinhalten, das geht dann ohne Fehler.
Aber eine beliebige strubbelige Bytefolge kann man nicht 
›encoden/decoden‹
1
#!python
2
ba = bytearray(x for x in range(256))
3
print(' '.join([f'{x:02x}' for x in ba]))

ba enthält alle Bytes von 0x00 … 0xff. Das kann man nicht in ein 
EncodingSchema pressen, weil's so etwas nicht gibt. Es sind einfach nur 
Rohdaten.
Wenn du also von einem Gerät gemischt Rohdaten und Text bekommst, müssen 
die getrennt werden bevor du encode auf den Textteil loslässt.

von Michael S. (rbs_phoenix)


Lesenswert?

Ich möchte die Daten in eine Datei schreiben und bisher hat die 
Schreibe-Funktion ein Fehler geworfen, da der String irgendwelche nicht 
plausiblen Zeichen enthält. Z.B.:
1
 newFile.write(image_data)
2
  File "C:\Program Files\Python310\lib\encodings\cp1252.py", line 19, in encode
3
    return codecs.charmap_encode(input,self.errors,encoding_table)[0]
4
UnicodeEncodeError: 'charmap' codec can't encode characters in position 180-181: character maps to <undefined>
Wenn ich die Datei mit "wb" geöffnet habe, darf ich kein String 
übergeben und wollte daher im Vorfeld über encode den String in "Bytes" 
wandeln, doch das hat auch nicht so funktioniert.

Ich habe es nun aber hinbekommen. Das encode hat nicht mit dem Encoding 
vom SerialPort() übereingestimmt. Nun habe ich beim encode und beim 
SerialPort() "28591" bzw. ISO-8859-1 gewählt und alles klappt. Das 
Encoding von den Daten ist leider nirgends in den Datenblättern etc. des 
Scanners aufgeführt. Daher war es ein bisschen raten, womit es denn geht 
(UTF8 und Windows-1252 zumindest nicht).

Ich danke euch allen dennoch für jeglichen Input.

Grüße
Michael

von Sebastian W. (wangnick)


Lesenswert?

Michael S. schrieb:
> Daher war es ein bisschen raten, womit es denn geht

Juhu, Problem ohne Verständnis gelöst!

LG, Sebastian

von Michael S. (rbs_phoenix)


Lesenswert?

Ich wäre interessiert an einer Erklärung bzw. dem Wissen, welches 
Verständnis ich hätte erlernen müssen, damit mir von vornherein klar 
sein würde, dass es genau dieses Encoding sein muss. Ich denke, wenn ich 
es erfahren könnte, dann in diesem Forum mit den vielen stets fehler- 
und makellosen Allwissenden.

von Martin M. (capiman)


Lesenswert?

Etwas offtopic: Hättest du einen Raspberry Pi da?
Könntest du deinen Scanner dort anschließen?

Fall ja und angeschlossen, gib mal

lsusb -v

ein. Damit bekommst du, welche USB-Devices der Raspberry sieht.
Kannst du den Output mal posten?

Es könnte sein, dass dein pyserial (!) Programm (Programm mit dem 
Fehler) auch auf dem Raspberry läuft. Wäre evt. auch einen Versuch wert.
(Und natürlich, ob sich dein finales Programm dort auch richtig 
verhält.)

Falls kein Raspberry:
Unter Windows gibt es den Gerätemanager.
Dort mal den COM-Port des Scanners suchen.
Unter Details -> Eigenschaften:Geräteinstanzpfad o.ä.
findet man die VID/PID. Bitte mal die Ausgabe posten.

von Sebastian W. (wangnick)


Lesenswert?

Michael S. schrieb:
> Ich wäre interessiert an einer Erklärung bzw. dem Wissen, welches
> Verständnis ich hätte erlernen müssen, damit mir von vornherein klar
> sein würde, dass es genau dieses Encoding sein muss.

Du mischst fröhlich Bytes und Symbole durcheinander. Die sollte man aber 
gerade in Python strikt voneinander trennen, sonst verirrt man sich in 
der Encoding-Hölle.

LG, Sebastian

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.