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
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...
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.
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:
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
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:
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:
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.
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.
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(xforxinrange(256))
3
print(' '.join([f'{x:02x}'forxinba]))
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.
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
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
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.
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.
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