Mittlerweile sollte es theoretisch einfach sein, über eine Webbrowser
direkt auf die serielle Schnittstelle zuzugreifen und z.B. Daten zum
Mikrocontroller zu übertragen:
https://tigoe.github.io/html-for-conndev/webSerial/
Nicht jeder Browser ist kompatibel:
https://entwickler.de/api/webserial-api-webapplikation-hardware-interaktion
Bei mir funktioniert das leider nicht. Webserial scheint von Chrome
unterstützt, aber er zeigt keine Schnittstellen an (Bild). Woran könnte
es liegen?
System: Ubuntu 20.04
Chromiung: Version 114.0.5735.198 (Offizieller Build) snap (64-Bit)
christoph@..:~$ groups
christoph .. dialout .. plugdev .. usbusers
Christoph M. schrieb:> Chromiung: Version 114.0.5735.198 (Offizieller Build) snap (64-Bit)
Würde vermuten dass "snap" daran schuld ist. Da wird der Chromium
nochmal in sein eigenes "Gefängnis" gesperrt, und wenn die Snap-Macher
nicht daran gedacht haben, auch serielle Ports dort rein zu lassen, wird
das nix.
Screenshot:
Version 114.0.5735.106 (Offizieller Build) built on Debian 12.0, running
on Debian 12.0 (64-Bit)
-> Installier dir einen Chrome, der kommt ohne Snap. Oder such eine
Anleitung, wie du die Debian-Chromium-Paketquellen in's Ubuntu
gefrickelt kriegst.
Edit:
Evtl reicht auch ein
>> sudo snap connect chromium:raw-usb
um das Chromium-Snap an USB-Devices ranzulassen.
Danke für die Hilfe :-)
>Evtl reicht auch ein>>> sudo snap connect chromium:raw-usb>um das Chromium-Snap an USB-Devices ranzulassen.
Das habe ich jetzt probiert, aber es scheint nicht zu funktionieren.
Ich habe das Snap-Paket mal deinstalliert und dann mit
https://askubuntu.com/questions/510056/how-to-install-google-chrome> ..> sudo apt-get update> sudo apt-get install google-chrome-stable
Chrome direkt installiert. Zeigt aber weiterhin keine Schnittstellen.
Funktioniert Webserial bei Dir? Wenn ja, auf welchem System?
Christoph M. schrieb:> Funktioniert Webserial bei Dir? Wenn ja, auf welchem System?
Ja. Debian 12, sowohl mit Chromium als auch mit Chrome.
Deine Installations-Anleitung ist von 2014, auch wenn die mal
aktualisiert wurde.
Keine Ahnung warum das über ein PPA läuft. Du kannst Chrome auch direkt
von hier herunterladen: https://www.google.com/chrome/
Und dann einfach das heruntergeladene .deb installieren. Das legt dann
auch einen sources.list Eintrag für automatische Updates an.
Kann aber sein, dass das PPA exakt dasselbe tut.
Chromium kann problemlos parallel installiert bleiben.
Kannst du denn aus dem Terminal auf das serielle Gerät zugreifen?
Irgendwann musste man unter Debian oder Ubuntu den eigenen User in die
dialout-Gruppe stecken.
Le X. schrieb:> Kannst du denn aus dem Terminal auf das serielle Gerät zugreifen?> Irgendwann musste man unter Debian oder Ubuntu den eigenen User in die> dialout-Gruppe stecken.
hat er im ersten Post geschrieben, er ist in dialout und plugdev, das
sollte schon reichen.
>Keine Ahnung warum das über ein PPA läuft. Du kannst Chrome auch direkt>von hier herunterladen: https://www.google.com/chrome/
Ok, vielen Dank für die Hilfe. Jetzt läuft es.
Mich würde interessieren, wie es bei den Windows-Usern so ist: Läuft es
dort ebenfalls? Ziel: eine platforunabhängige GUI für Mikrocontroller.
Deshalb wäre es gut, wenn Webserial auf allen Platformen einigermaßen
problemlos läuft.
Was mir auffällt: Das Beispiel vom Eingangspost läuft nicht so gut. Die
empfangenen Zeichen sind nicht sonderlich gut synchronisiert.
https://tigoe.github.io/html-for-conndev/webSerial/
Das Terminal
https://googlechromelabs.github.io/serial-terminal/
läuft, aber wenn ich senden will scheint es Probleme mit CR/LF zu geben.
Da wäre die Frage, ob das bei Windows per default anders ist.
Christoph M. schrieb:> läuft, aber wenn ich senden will scheint es Probleme mit CR/LF zu geben.
Ändert der "Convert EOL"-Knopf das?
Aber eigentlich egal, wenn du deine eigene Oberfläche für deine eigenen
µC-Projekte baust, kannst du das Zeilenende-Zeichen auf beiden Seiten
passend festlegen.
Auch das Buffering für empfangene Zeichen kannst du da so handhaben, wie
es für deine Anwendung passt.
Im Moment versuche ich, mit Webserial Daten zu senden und zu empfangen.
Dazu ein kurzes Beispiel: Drückt man den "send" Knopf, wird das Wort
"hello" gesendet. Alle Zeichen von der seriellen Schnittstelle sollen im
Browserfenster angezeigt werden.
Aus mir unerfindlichen Gründen wird nur das "hello" gesendet. Man sieht
es auf dem Oszilloskop. Das Empfangen will aber einfach nicht
funktionieren.
Hat jemand eine Idee?
Webserial Code:
1
<!DOCTYPE html>
2
<html>
3
<head>
4
<title>Web Serial Example</title>
5
</head>
6
<body>
7
<buttonid="connectButton">Connect to Serial Device</button>
Christoph M. schrieb:> port.addEventListener('readable', onSerialDataReceived);
gibt es nicht. SerialPort hat events für connect/disconnect, aber nicht
für "readable".
Brauchst du auch nicht, du kannst einfach "async" vom reader lesen.
d.H.:
Ändere Zeile
port.readable.addEventListener('readable', onSerialDataReceived);
in
onSerialDataReceived();
(ohne "await"!)
Und schon funktioniert dein Beispielcode.
(Vorschlag auf kleinste Änderung hin ausgewählt, nicht auf schönsten
Code)
Nachtrag: Falls du dich wunderst, warum ich "Hello" im receivedData
stehen habe und kein
H
e
l
l
o
Ich hab keinen Arduino mit deinem Code angeklemmt, sondern einfach einen
USB->Serial Stöpsel mit Jumper über Rx/Tx.
Vielen Dank, funktioniert super.
>Ich hab keinen Arduino mit deinem Code angeklemmt, sondern einfach einen>USB->Serial Stöpsel mit Jumper über Rx/Tx.
Gute Idee. Das Arduino-Script habe ich für's debugging gemacht, weil ich
vermutete, dass Webserial vielleicht für den Empfang CR und NL braucht.
War wohl nicht so ;-)
Hier mal eine Version, die einen Zähler sendet und die empfangenen Werte
in einem Scrollfenster darstellt. Wichtig ist, dass erst beim Empfang
des NL upgedatet wird, sonst hat man immer Unterbrechungen in der
Ausgabe.
1
<!DOCTYPE html>
2
<html>
3
<head>
4
<title>Web Serial Example</title>
5
</head>
6
<body>
7
<buttonid="connectButton">Connect to Serial Device</button>
Was mir jetzt noch fehlt: Es sollen die empfangenen Werte in
verschiedene Textausgaben umgeleitet werden.
Empfang Textausgabe
======== ============
zaehlerA -> textfeld1
zaehlerA -> textfeld2
zaehlerA -> textfeld3
Christoph M. schrieb:> const receivedText = new TextDecoder().decode(value);> receivedBuffer += receivedText;
Kleine Anmerkung, das kann später schwer zu findende "Heisenbugs" geben:
der Standard-Textdecoder ist für UTF-8, und da kann ein Zeichen mehrere
Bytes lang sein.
Der gelesene Chunk von der Seriellen kann mitten in einem UTF-8-Zeichen
enden, das Zeichen ginge dann verloren.
Der TextDecoder kann das handhaben, aber natürlich nur, wenn du nicht
ständig neue Decoder erstellst, sondern denselben TextDecoder für den
gesamten Datenstrom beibehältst. (Dann ".decode(value, { stream: !done
});" mit dem "done"-Boolean aus dem reader.read() )
Oder: TextDecoder mit Ascii initialisieren, da ist "ein Byte"=="ein
Buchstabe".
Oder: Gleich einen "TextDecoderStream" nehmen, mit ".pipeThrough()"
direkt an den Serial-reader-Stream koppeln.
Übrigens, wenn du sowieso schon am "Streamen" der Daten bist:
>Kleine Anmerkung, das kann später schwer zu findende "Heisenbugs" geben:>der Standard-Textdecoder ist für UTF-8, und da kann ein Zeichen mehrere>Bytes lang sein.
Danke für den Hinweis. Sollte der Mikrocontroller nicht immer ganze
Bytes senden?
Das verstehe ich nicht ganz. Wie kann ich den LineTransformer dann
lesen?
P.s: kleiner Seitenbranch ..
Ich habe gerade mal die Pipico "Scopy" App auf dem Handy installiert
Beitrag "PiPico Oszilloskop"
den Pipico mit der Scopy Firmware geflasht. Es funktioniert und die App
kann mit dem Pipico kommunizieren.
Dann habe ich
>Serial-Terminal über Chromium zu einem ESP8266-Patinchen.https://googlechromelabs.github.io/serial-terminal/
ausprobiert, aber behauptet Chrome, das nicht zu unterstützen. Ich habe
ein älteres S8, vielleicht liegt es daran, oder geht es grundsätzlich
nicht?
Christoph M. schrieb:> Danke für den Hinweis. Sollte der Mikrocontroller nicht immer ganze> Bytes senden?
Ganze Bytes schon, aber keine ganzen Buchstaben (Du verwendest UTF-8)
ein 'Ä' ist da z.B. 0xC3 0x84
Wenn du dem TextDecoder (wegen blöd gelegenem Chunk) zuerst ein 0xC3
gibst, und danach einem neuem TextDecoder das 0x84, kann keiner von
Beiden was damit anfangen.
Deshalb: TextDecoder nicht immer wieder frisch mit "new" anlegen,
solange leben lassen wie der ReadStream lebt. Und Daten mit
"stream:true" einfüllen, wenn die Daten gestreamt werden, wie das am
Seriellen Port nunmal so ist. Wenn das zu kompliziert ist: der
TextDecoderStream kapselt das für dich weg.
Oder zu verzichtest auf UTF-8, und nimmst z.B. Ascii oder ISO-8859-15,
dann musst du das aber dem TextDecoder im Konstruktor mitteilen.
Christoph M. schrieb:> Das verstehe ich nicht ganz. Wie kann ich den LineTransformer dann> lesen?
wie bisher auch, nur dass eben immer ein einzeiliger String rausfällt.
1
constreader=readLineStream.getReader();
2
while(true){
3
const{value,done}=awaitreader.read();
4
// value ist jetzt ein string, und immer eine Zeile.
5
...
Oder du packst dir einen WritableStream hinter den lineTransformer, dann
kannst du auch ohne "while(true)-Schleife immer dann einen Callback
bekommen, wenn wieder eine Zeile komplett gelesen wurde.
...
Android: Nein.
https://caniuse.com/web-serial
bzw. Jain: Über das Polyfill, was per Web-USB direkt auf USB->Seriell
Adapter zugreift, und deren "Treiber" in Javascript nachgebaut hat, geht
es evtl.
https://caniuse.com/webusb
Ich hab das mal in dein Beispielprogramm zusammengepackt.
im Endeffekt:
1
constlineReceiver=newWritableStream({
2
write:lineReceived
3
});
4
port.readable
5
.pipeThrough(newTextDecoderStream())
6
.pipeThrough(lineTransformer)
7
.pipeTo(lineReceiver);
Bewirkt dass "lineReceived" als Callback automatisch ausgeführt wird,
sobald eine Zeile komplett ist.
Beim Senden (am Hallo-Knopf) habe ich eingebaut, dass er einen Teil
schön langsam Byteweise schickt, und einen Block mit mehreren Zeilen in
einem Rutsch, um zu Zeigen dass so der Text-Decoder trotz Sonderzeichen
nicht aus dem Tritt kommt, und der lineTransformer auch bei mehreren
Zeilen in einem Chunk alle Zeilen brav an den callback übergibt.
Zum Testen hatte ich wieder Rx/Tx gebrückt.
Vielen Dank, das scheint gut zu funktionieren.
Zuerst war ich durch die Delays irritiert. Aber wenn man sie raus macht,
funktioniert es gut.
Ich habe das Beispiel mal so verändert, dass beim Drücken des Tasters
eine Zähler erhöht, gesendet und der Empfangsstring dann dargestellt
wird.
Christoph M. schrieb:> Zuerst war ich durch die Delays irritiert. Aber wenn man sie raus macht,> funktioniert es gut.
Wie geschrieben, die waren zum Testen drinnen, um eine maximal
ungünstige Unterteilung des Datenstroms in gelesene Chunks zu erreichen.
Wenn der Test mit so einem Worst-Case funktioniert, wird's auch in der
Realität bei wesentlich "angenehmeren" Datenblöcken funktionieren.
Εrnst B. schrieb:> TextDecoder mit Ascii initialisieren
Statt ASCII sollte man ISO8859-1 bzw. CP1252 (auch "Windows ANSI"
genannt) verwenden, dann lassen sich auch die üblichen Sonderzeichen,
die im mitteleuropäischen Raum üblich sind, verwenden. Jedes
darstellbare Zeichen ist dann genau ein Byte groß.
Allerdings sollte man auch den Quelltext für den µC in dieser Codierung
erstellen.
So schick UTF-8 an mancher Stelle auch sein mag, so extrem ungeeignet
ist es bei µC-Anwendungen.
Terminalprogramme wie z.B. Teraterm verwenden ebenfalls diese
Zeichencodierung.
Harald K. schrieb:> Statt ASCII sollte man ISO8859-1 bzw. CP1252 (auch "Windows ANSI"> genannt) verwenden, dann lassen sich auch die üblichen Sonderzeichen,> die im mitteleuropäischen Raum üblich sind, verwenden. Jedes> darstellbare Zeichen ist dann genau ein Byte groß.
ISO8859-15 hatte ich vorgeschlagen, €.
Aber es tut ja nicht weh den TextDecoder einfach richtig zu verwenden,
dann funktioniert es mit jeder Kodierung, egal ob 7-Bit-ASCII oder
UTF-32.
Und man muss sich (außer dem Einstellen der verwendeten Kodierung im
Konstruktor) nie wieder Gedanken darum machen.
Εrnst B. schrieb:> Aber es tut ja nicht weh den TextDecoder einfach richtig zu verwenden,> dann funktioniert es mit jeder Kodierung, egal ob 7-Bit-ASCII oder> UTF-32.
Dann muss aber die Gegenstelle (oft ein µC) auch davon wissen.
strlen und UTF-8 kann lustig werden.
Harald K. schrieb:> Dann muss aber die Gegenstelle (oft ein µC) auch davon wissen.
Na und? Wenn der µC es nicht kann oder nicht können soll, nimmt man eben
einen 8-Bit-Zeichensatz.
Ist trotzdem kein Grund, auf PC-Seite das Zeichensatz-Dekodieren
absichtlich falsch zu implementieren, wenn der korrekte Code sogar noch
kürzer, einfacher und resourcenschonender ist.
Christoph M. schrieb:> Ziel: eine platforunabhängige GUI für Mikrocontroller. Deshalb wäre es> gut, wenn Webserial auf allen Platformen einigermaßen problemlos läuft.
Mich würde brennend interessieren ob das jemand bestätigen kann! Oder
halt auf den meisten Plattformen halt...
Gruss Chregu
Christian M. schrieb:> Mich würde brennend interessieren ob das jemand bestätigen kann! Oder> halt auf den meisten Plattformen halt...
sh. oben.
Εrnst B. schrieb:> Android: Nein.> https://caniuse.com/web-serial> bzw. Jain: Über das Polyfill, was per Web-USB direkt auf USB->Seriell> Adapter zugreift, und deren "Treiber" in Javascript nachgebaut hat, geht> es evtl.> https://caniuse.com/webusb
Also:
Android bei manchen USB->Seriell Wandlern per Polyfill,
iPhone schaut in die Röhre,
Windows und Linux: Problemlos, zumindest solange man nicht auf Windows95
o.Ä. festhängt und deshalb keine Browser-Updates mehr kriegt.
Desktop-Mac: Hab ich nicht getestet, vermutlich nur mit Chrome, Safari
bleibt außen vor.
Mit den Daten von caniuse: 30% aller User deckst du damit ab, am Desktop
ca. 75%, wenn die Leute bereit sind einen vmtl. schon installierten,
aber selten verwendeten Browser zu Starten: Schätze 80-90%.
Inzwischen stammt eben ein Großteil des Web-Traffics von
Telefonen/Tablets, deshalb der große Unterschied zwischen "alle User"
und "Desktop only"
Εrnst B. schrieb:> wie bisher auch, nur dass eben immer ein einzeiliger String rausfällt.> const reader=readLineStream.getReader();> while (true) {> const { value, done } = await reader.read();> // value ist jetzt ein string, und immer eine Zeile.
WTF, die ReadableStream API ist praktisch ein AsyncIterator, aber mit
minimalen inkompatiblen unterschieden!?!
Der reader von einem ReadableStream hat die Methoden read(), cancel(),
releaseLock(). Ein AsyncGenerator hat next(), return(), throw().
read() und next() geben beide eine Promise zurück, beide resultieren in
einem Objekt mit {value, done}.
Und sie implementieren noch nicht einmal Symbol.asyncIterator! Würden
sie das machen, gienge ein simples "for await"
Naja, muss man halt manuell konvertieren:
Daniel A. (daniel-a)
>WTF, die ReadableStream API ist praktisch ein AsyncIterator, aber mit>minimalen inkompatiblen unterschieden!?!
Schön wäre es, wenn du aus deinem Beitrag ein ausführbares Beispiel
machst, damit die Leute schnell ausprobieren können, ob es auch wirklich
funktioniert und besser ist, als die von Ernst vorgeschlagene Methode.
Hier ein Beispiel, wie das aussehen kann:
Beitrag "Re: Webserial Chrome"
Christoph M. schrieb:> ob es auch wirklich> funktioniert und besser ist, als die von Ernst vorgeschlagene Methode.
Er hat ja nicht wirklich was daran geändert, nur den Stream-Reader von
Chrome so umgepatcht (auf den Stand vom Firefox gebracht...), dass man
statt dem umständlichen
1
while(true){
2
const{value,done}=awaitreader.read();
das schönere
1
forawait(constvalueofreader){
schreiben kann.
Wenn man die Variante mit dem "lineReceived"-Callback verwenden mag
ändert sich nix, da entfällt die Schleife komplett.