Mittlerweile sollte es theoretisch einfach sein, über eine Webbrowser direkt auf die serielle Schnittstelle zuzugreifen und z.B. Daten zum Mikrocontroller zu übertragen:
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.
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.
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.
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>
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>
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?
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.
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.
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.
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.
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.
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.
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.
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"
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"
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.
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.