Python: pySerial Lese-Fehler unter Win10

OP #7406698
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

Angehängte Dateien:
#7406712
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...

OP #7406751
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.

OP #7409058
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

OP #7409106
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

#7409144
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.

OP #7409478
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

OP #7409497
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.

#7409517
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.

#7409694
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

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren