1. Wprowadzenie
Ostatnia aktualizacja: 21 lipca 2020 r.
Co utworzysz
W tym samouczku utworzysz stronę internetową, która używa interfejsu Web Serial API do interakcji z płytką BBC micro:bit w celu wyświetlania obrazów na matrycy LED 5x5. Dowiesz się więcej o interfejsie Web Serial API i o tym, jak używać strumieni do odczytu, zapisu i przekształcania do komunikacji z urządzeniami szeregowymi za pomocą przeglądarki.

Czego się nauczysz
- Otwieranie i zamykanie portu szeregowego w internecie
- Jak używać pętli odczytu do obsługi danych ze strumienia wejściowego
- Jak wysyłać dane za pomocą strumienia zapisu
Czego potrzebujesz
- Płytka BBC micro:bit z najnowszym oprogramowaniem Espruino.
- najnowsza wersja Chrome (80 lub nowsza);
- znajomość języków HTML, CSS i JavaScript oraz Narzędzi deweloperskich w Chrome;
Wybraliśmy micro:bit do tego samouczka, ponieważ jest niedrogi, ma kilka wejść (przyciski) i wyjść (wyświetlacz LED 5x5) oraz może zapewniać dodatkowe wejścia i wyjścia. Szczegółowe informacje o możliwościach micro:bita znajdziesz na stronie BBC micro:bit w witrynie Espruino.
2. Informacje o interfejsie Web Serial API
Web Serial API umożliwia witrynom odczytywanie danych z urządzenia szeregowego i zapisywanie w nim danych za pomocą skryptów. Interfejs API łączy internet ze światem fizycznym, umożliwiając witrynom komunikację z urządzeniami szeregowymi, takimi jak mikrokontrolery i drukarki 3D.
Istnieje wiele przykładów oprogramowania sterującego, które zostało zbudowane przy użyciu technologii internetowych. Na przykład:
W niektórych przypadkach te witryny komunikują się z urządzeniem za pomocą natywnej aplikacji agenta, która jest instalowana ręcznie przez użytkownika. W innych przypadkach aplikacja jest dostarczana w postaci spakowanej aplikacji natywnej za pomocą platformy takiej jak Electron. W innych przypadkach użytkownik musi wykonać dodatkowy krok, np. skopiować skompilowaną aplikację na urządzenie za pomocą pamięci USB.
Wygodę użytkownika można zwiększyć, zapewniając bezpośrednią komunikację między witryną a urządzeniem, którym steruje.
3. Przygotowania
Pobierz kod
Wszystko, czego potrzebujesz do tego ćwiczenia, umieściliśmy w projekcie Glitch.
- Otwórz nową kartę przeglądarki i wejdź na stronę https://web-serial-codelab-start.glitch.me/.
- Kliknij link Remix Glitch, aby utworzyć własną wersję projektu początkowego.
- Kliknij przycisk Pokaż, a potem wybierz W nowym oknie, aby zobaczyć działanie kodu.
4. Otwieranie połączenia szeregowego
Sprawdzanie, czy interfejs Web Serial API jest obsługiwany
Najpierw sprawdź, czy Web Serial API jest obsługiwany w bieżącej przeglądarce. Aby to zrobić, sprawdź, czy serial znajduje się w navigator.
W zdarzeniu DOMContentLoaded dodaj do projektu ten kod:
script.js - DOMContentLoaded
// CODELAB: Add feature detection here.
const notSupported = document.getElementById('notSupported');
notSupported.classList.toggle('hidden', 'serial' in navigator);
Sprawdza, czy interfejs Web Serial jest obsługiwany. Jeśli tak, ten kod ukrywa baner z informacją, że interfejs Web Serial nie jest obsługiwany.
Wypróbuj
- Załaduj stronę.
- Sprawdź, czy na stronie nie wyświetla się czerwony baner z informacją, że serial internetowy nie jest obsługiwany.
Otwórz port szeregowy
Następnie musimy otworzyć port szeregowy. Podobnie jak większość nowoczesnych interfejsów API, Web Serial API jest asynchroniczny. Zapobiega to blokowaniu interfejsu podczas oczekiwania na dane wejściowe, ale jest też ważne, ponieważ dane szeregowe mogą być odbierane przez stronę internetową w dowolnym momencie i musimy mieć możliwość ich nasłuchiwania.
Komputer może mieć wiele urządzeń szeregowych, więc gdy przeglądarka próbuje wysłać żądanie portu, wyświetla prośbę o wybranie urządzenia, z którym ma się połączyć.
Dodaj do projektu ten kod:
script.js - connect()
// CODELAB: Add code to request & open port here.
// - Request a port and open a connection.
port = await navigator.serial.requestPort();
// - Wait for the port to open.
await port.open({ baudrate: 9600 });
Wywołanie requestPort wyświetla użytkownikowi pytanie, z którym urządzeniem chce się połączyć. Zadzwonienie pod numer port.open otworzy port. Musimy też podać szybkość, z jaką chcemy komunikować się z urządzeniem szeregowym. BBC micro:bit używa połączenia 9600 baud między układem USB-serial a głównym procesorem.
Połączmy też przycisk łączenia i sprawmy, aby po kliknięciu przez użytkownika wywoływał funkcję connect().
Dodaj do projektu ten kod:
script.js - clickConnect()
// CODELAB: Add connect code here.
await connect();
Wypróbuj
Nasz projekt ma teraz minimalną konfigurację, która pozwala rozpocząć pracę. Kliknięcie przycisku Połącz powoduje wyświetlenie prośby o wybranie urządzenia szeregowego, z którym chcesz się połączyć, a następnie łączy się z micro:bitem.
- Odśwież stronę.
- Kliknij przycisk Połącz.
- W oknie wyboru portu szeregowego wybierz urządzenie BBC micro:bit i kliknij Połącz.
- Na karcie powinna być widoczna ikona wskazująca, że połączono się z urządzeniem szeregowym:

Skonfiguruj strumień wejściowy, aby nasłuchiwać danych z portu szeregowego
Po nawiązaniu połączenia musimy skonfigurować strumień wejściowy i czytnik, aby odczytywać dane z urządzenia. Najpierw pobierzemy z portu strumień do odczytu, wywołując funkcję port.readable. Wiemy, że otrzymamy z urządzenia tekst, więc przekierujemy go przez dekoder tekstu. Następnie uzyskamy dostęp do czytnika i rozpoczniemy pętlę odczytu.
Dodaj do projektu ten kod:
script.js - connect()
// CODELAB: Add code to read the stream here.
let decoder = new TextDecoderStream();
inputDone = port.readable.pipeTo(decoder.writable);
inputStream = decoder.readable;
reader = inputStream.getReader();
readLoop();
Pętla odczytu to funkcja asynchroniczna, która działa w pętli i czeka na treści bez blokowania głównego wątku. Gdy pojawią się nowe dane, czytnik zwraca 2 właściwości: value i wartość logiczną done. Jeśli wartość done to „prawda”, port został zamknięty lub nie są już przesyłane żadne dane.
Dodaj do projektu ten kod:
script.js - readLoop()
// CODELAB: Add read loop here.
while (true) {
const { value, done } = await reader.read();
if (value) {
log.textContent += value + '\n';
}
if (done) {
console.log('[readLoop] DONE', done);
reader.releaseLock();
break;
}
}
Wypróbuj
Nasz projekt może teraz połączyć się z urządzeniem i dodać do elementu dziennika wszystkie dane otrzymane z urządzenia.
- Odśwież stronę.
- Kliknij przycisk Połącz.
- W oknie wyboru portu szeregowego wybierz urządzenie BBC micro:bit i kliknij Połącz.
- Powinno się wyświetlić logo Espruino:

Konfigurowanie strumienia wyjściowego do wysyłania danych do portu szeregowego
Transmisja szeregowa jest zwykle dwukierunkowa. Oprócz odbierania danych z portu szeregowego chcemy też wysyłać do niego dane. Podobnie jak w przypadku strumienia wejściowego, do micro:bita będziemy wysyłać tylko tekst w strumieniu wyjściowym.
Najpierw utwórz strumień kodera tekstu i przekaż go do port.writeable.
script.js - connect()
// CODELAB: Add code setup the output stream here.
const encoder = new TextEncoderStream();
outputDone = encoder.readable.pipeTo(port.writable);
outputStream = encoder.writable;
Po połączeniu szeregowym z oprogramowaniem Espruino płytka BBC micro:bit działa jak pętla odczyt-obliczenie-wydruk (REPL) w JavaScript, podobnie jak w przypadku powłoki Node.js. Następnie musimy podać metodę wysyłania danych do strumienia. Poniższy kod pobiera zapisującego ze strumienia wyjściowego, a następnie używa funkcji write do wysyłania każdego wiersza. Każda wysłana linia zawiera znak nowego wiersza (\n), który informuje micro:bit o konieczności oceny wysłanego polecenia.
script.js - writeToStream()
// CODELAB: Write to output stream
const writer = outputStream.getWriter();
lines.forEach((line) => {
console.log('[SEND]', line);
writer.write(line + '\n');
});
writer.releaseLock();
Aby przywrócić system do znanego stanu i zapobiec odsyłaniu przez niego wysyłanych przez nas znaków, musimy wysłać CTRL-C i wyłączyć echo.
script.js - connect()
// CODELAB: Send CTRL-C and turn off echo on REPL
writeToStream('\x03', 'echo(false);');
Wypróbuj
Nasz projekt może teraz wysyłać i odbierać dane z urządzenia micro:bit. Sprawdźmy, czy możemy prawidłowo wysłać polecenie:
- Odśwież stronę.
- Kliknij przycisk Połącz.
- W oknie wyboru portu szeregowego wybierz urządzenie BBC micro:bit i kliknij Połącz.
- Otwórz kartę Konsola w Narzędziach deweloperskich w Chrome i wpisz
writeToStream('console.log("yes")');
Na stronie powinna być widoczna informacja podobna do tej:

5. Sterowanie matrycą LED
Tworzenie ciągu siatki macierzy
Aby sterować matrycą LED na micro:bicie, musimy wywołać funkcję show(). Ta metoda wyświetla grafikę na wbudowanym ekranie LED o wymiarach 5x5. Przyjmuje liczbę binarną lub ciąg znaków.
Przejdziemy przez pola wyboru i wygenerujemy tablicę jedynek i zer, które będą wskazywać, które pola są zaznaczone, a które nie. Następnie musimy odwrócić kolejność elementów w tablicy, ponieważ kolejność pól wyboru jest odwrotna do kolejności diod LED w matrycy. Następnie przekonwertujemy tablicę na ciąg znaków i utworzymy polecenie do wysłania do micro:bita.
script.js - sendGrid()
// CODELAB: Generate the grid
const arr = [];
ledCBs.forEach((cb) => {
arr.push(cb.checked === true ? 1 : 0);
});
writeToStream(`show(0b${arr.reverse().join('')})`);
Połącz pola wyboru, aby zaktualizować macierz
Następnie musimy nasłuchiwać zmian w polach wyboru i w razie ich wystąpienia wysyłać informacje do urządzenia micro:bit. W kodzie wykrywania cech (// CODELAB: Add feature detection here.) dodaj ten wiersz:
script.js - DOMContentLoaded
initCheckboxes();
Zresetujmy też siatkę, gdy micro:bit zostanie po raz pierwszy podłączony, aby wyświetlał uśmiechniętą buźkę. Funkcja drawGrid() jest już dostępna. Ta funkcja działa podobnie do funkcji sendGrid(). Przyjmuje tablicę jedynek i zer i zaznacza odpowiednie pola wyboru.
script.js - clickConnect()
// CODELAB: Reset the grid on connect here.
drawGrid(GRID_HAPPY);
sendGrid();
Wypróbuj
Teraz, gdy strona otworzy połączenie z mikro:bitem, wyśle uśmiechniętą buźkę. Kliknięcie pól wyboru spowoduje zaktualizowanie wyświetlacza na matrycy LED.
- Odśwież stronę.
- Kliknij przycisk Połącz.
- W oknie wyboru portu szeregowego wybierz urządzenie BBC micro:bit i kliknij Połącz.
- Na matrycy LED micro:bit powinien pojawić się uśmiech.
- Narysuj inny wzór na matrycy LED, zmieniając zaznaczenie pól wyboru.
6. Podłącz przyciski micro:bita
Dodawanie zdarzenia zegarka na przyciskach micro:bita
Na płytce micro:bit znajdują się 2 przyciski, po jednym z każdej strony matrycy LED. Espruino udostępnia funkcję setWatch, która wysyła zdarzenie lub wywołanie zwrotne po naciśnięciu przycisku. Chcemy, aby funkcja reagowała na oba przyciski, dlatego będzie ona ogólna i będzie wyświetlać szczegóły zdarzenia.
script.js - watchButton()
// CODELAB: Hook up the micro:bit buttons to print a string.
const cmd = `
setWatch(function(e) {
print('{"button": "${btnId}", "pressed": ' + e.state + '}');
}, ${btnId}, {repeat:true, debounce:20, edge:"both"});
`;
writeToStream(cmd);
Następnie musimy podłączyć oba przyciski (na płytce micro:bit oznaczono je jako BTN1 i BTN2) za każdym razem, gdy port szeregowy jest połączony z urządzeniem.
script.js - clickConnect()
// CODELAB: Initialize micro:bit buttons.
watchButton('BTN1');
watchButton('BTN2');
Wypróbuj
Oprócz wyświetlania uśmiechniętej buźki po połączeniu naciśnięcie dowolnego przycisku na micro:bicie spowoduje dodanie do strony tekstu informującego o tym, który przycisk został naciśnięty. Najprawdopodobniej każda postać będzie w osobnym wierszu.
- Odśwież stronę.
- Kliknij przycisk Połącz.
- W oknie wyboru portu szeregowego wybierz urządzenie BBC micro:bit i kliknij Połącz.
- Na matrycy LED micro:bit powinien pojawić się uśmiech.
- Naciśnij przyciski na micro:bicie i sprawdź, czy do strony dodawany jest nowy tekst ze szczegółami naciśniętego przycisku.
7. Używanie strumienia przekształceń do analizowania przychodzących danych
Podstawowa obsługa strumieni
Gdy naciśniesz jeden z przycisków micro:bit, urządzenie wyśle dane do portu szeregowego za pomocą strumienia. Strumienie są bardzo przydatne, ale mogą też stanowić wyzwanie, ponieważ niekoniecznie otrzymasz wszystkie dane naraz, a mogą one być podzielone na dowolne części.
Aplikacja obecnie drukuje przychodzący strumień w miarę jego docierania (w readLoop). W większości przypadków każdy znak znajduje się w osobnym wierszu, co nie jest zbyt przydatne. Najlepiej, aby strumień był podzielony na poszczególne wiersze, a każda wiadomość była wyświetlana w osobnym wierszu.
Przekształcanie strumieni za pomocą TransformStream
W tym celu możemy użyć strumienia przekształcającego ( TransformStream), który umożliwia analizowanie strumienia przychodzącego i zwracanie przeanalizowanych danych. Strumień przekształcający może znajdować się między źródłem strumienia (w tym przypadku micro:bit) a elementem, który go wykorzystuje (w tym przypadku readLoop), i może stosować dowolne przekształcenie przed ostatecznym wykorzystaniem. Wyobraź sobie linię montażową: gdy widżet przechodzi przez kolejne etapy, każdy z nich go modyfikuje, tak że po dotarciu do miejsca docelowego jest w pełni funkcjonalny.
Więcej informacji znajdziesz w artykule MDN's Streams API concepts (w języku angielskim).
Przekształcanie strumienia za pomocą LineBreakTransformer
Utwórzmy klasę LineBreakTransformer, która będzie przyjmować strumień i dzielić go na części na podstawie znaków końca wiersza (\r\n). Klasa musi mieć 2 metody: transform i flush. Metoda transform jest wywoływana za każdym razem, gdy strumień odbiera nowe dane. Może umieścić dane w kolejce lub zapisać je na później. Metoda flush jest wywoływana po zamknięciu strumienia i obsługuje wszystkie dane, które nie zostały jeszcze przetworzone.
W metodzie transform dodamy nowe dane do container, a następnie sprawdzimy, czy w container występują podziały wierszy. Jeśli tak, podziel go na tablicę, a następnie przeiteruj wiersze, wywołując funkcję controller.enqueue(), aby wysłać przeanalizowane wiersze.
script.js - LineBreakTransformer.transform()
// CODELAB: Handle incoming chunk
this.container += chunk;
const lines = this.container.split('\r\n');
this.container = lines.pop();
lines.forEach(line => controller.enqueue(line));
Gdy strumień zostanie zamknięty, po prostu opróżnimy kontener z pozostałych danych za pomocą funkcji enqueue.
script.js - LineBreakTransformer.flush()
// CODELAB: Flush the stream.
controller.enqueue(this.container);
Na koniec musimy przekierować strumień przychodzący przez nowy element LineBreakTransformer. Pierwotny strumień wejściowy był przekazywany tylko przez TextDecoderStream, więc musimy dodać dodatkowy pipeThrough, aby przekazywać go przez nowy LineBreakTransformer.
script.js - connect()
// CODELAB: Add code to read the stream here.
let decoder = new TextDecoderStream();
inputDone = port.readable.pipeTo(decoder.writable);
inputStream = decoder.readable
.pipeThrough(new TransformStream(new LineBreakTransformer()));
Wypróbuj
Teraz, gdy naciśniesz jeden z przycisków micro:bit, wydrukowane dane powinny zostać zwrócone w jednym wierszu.
- Odśwież stronę.
- Kliknij przycisk Połącz.
- W oknie wyboru portu szeregowego wybierz urządzenie BBC micro:bit i kliknij Połącz.
- Na matrycy LED micro:bit powinien pojawić się uśmiech.
- Naciśnij przyciski na micro:bicie i sprawdź, czy widzisz coś podobnego do tego:

Przekształcanie strumienia za pomocą JSONTransformer
Możemy spróbować przeanalizować ciąg znaków w formacie JSON w readLoop, ale zamiast tego utwórzmy bardzo prosty transformator JSON, który przekształci dane w obiekt JSON. Jeśli dane nie są prawidłowym formatem JSON, po prostu zwróć to, co zostało przesłane.
script.js - JSONTransformer.transform
// CODELAB: Attempt to parse JSON content
try {
controller.enqueue(JSON.parse(chunk));
} catch (e) {
controller.enqueue(chunk);
}
Następnie przekieruj strumień przez JSONTransformer po tym, jak przejdzie przez LineBreakTransformer. Dzięki temu możemy zachować JSONTransformer w prostej formie, ponieważ wiemy, że JSON będzie zawsze wysyłany w jednym wierszu.
script.js - connect
// CODELAB: Add code to read the stream here.
let decoder = new TextDecoderStream();
inputDone = port.readable.pipeTo(decoder.writable);
inputStream = decoder.readable
.pipeThrough(new TransformStream(new LineBreakTransformer()))
.pipeThrough(new TransformStream(new JSONTransformer()));
Wypróbuj
Teraz, gdy naciśniesz jeden z przycisków micro:bit, na stronie powinna pojawić się ikona [object Object].
- Odśwież stronę.
- Kliknij przycisk Połącz.
- W oknie wyboru portu szeregowego wybierz urządzenie BBC micro:bit i kliknij Połącz.
- Na matrycy LED micro:bit powinien pojawić się uśmiech.
- Naciśnij przyciski na micro:bicie i sprawdź, czy widzisz coś podobnego do tego:
Reagowanie na naciśnięcia przycisków
Aby reagować na naciśnięcia przycisków micro:bit, zaktualizuj blok readLoop, aby sprawdzić, czy otrzymane dane to object z właściwością button. Następnie wywołaj funkcję buttonPushed, aby obsłużyć naciśnięcie przycisku.
script.js - readLoop()
const { value, done } = await reader.read();
if (value && value.button) {
buttonPushed(value);
} else {
log.textContent += value + '\n';
}
Po naciśnięciu przycisku na płytce micro:bit wyświetlacz na matrycy LED powinien się zmienić. Aby ustawić macierz, użyj tego kodu:
script.js - buttonPushed()
// CODELAB: micro:bit button press handler
if (butEvt.button === 'BTN1') {
divLeftBut.classList.toggle('pressed', butEvt.pressed);
if (butEvt.pressed) {
drawGrid(GRID_HAPPY);
sendGrid();
}
return;
}
if (butEvt.button === 'BTN2') {
divRightBut.classList.toggle('pressed', butEvt.pressed);
if (butEvt.pressed) {
drawGrid(GRID_SAD);
sendGrid();
}
}
Wypróbuj
Teraz, gdy naciśniesz jeden z przycisków micro:bita, matryca LED powinna zmienić się na uśmiechniętą lub smutną buźkę.
- Odśwież stronę.
- Kliknij przycisk Połącz.
- W oknie wyboru portu szeregowego wybierz urządzenie BBC micro:bit i kliknij Połącz.
- Na matrycy LED micro:bit powinien pojawić się uśmiech.
- Naciśnij przyciski na micro:bicie i sprawdź, czy zmienia się matryca LED.
8. Zamykanie portu szeregowego
Ostatnim krokiem jest podłączenie funkcji rozłączania, aby zamknąć port, gdy użytkownik skończy.
Zamknij port, gdy użytkownik kliknie przycisk Połącz/Odłącz
Gdy użytkownik kliknie przycisk Połącz lub Odłącz, musimy zamknąć połączenie. Jeśli port jest już otwarty, wywołaj disconnect()i zaktualizuj interfejs, aby wskazać, że strona nie jest już połączona z urządzeniem szeregowym.
script.js - clickConnect()
// CODELAB: Add disconnect code here.
if (port) {
await disconnect();
toggleUIConnected(false);
return;
}
Zamknij strumienie i port
W funkcji disconnect musimy zamknąć strumień wejściowy, strumień wyjściowy i port. Aby zamknąć strumień wejściowy, wywołaj funkcję reader.cancel(). Wywołanie funkcji cancel jest asynchroniczne, więc musimy użyć funkcji await, aby poczekać na jego zakończenie:
script.js - disconnect()
// CODELAB: Close the input stream (reader).
if (reader) {
await reader.cancel();
await inputDone.catch(() => {});
reader = null;
inputDone = null;
}
Aby zamknąć strumień wyjściowy, pobierz writer, wywołaj close() i poczekaj, aż obiekt outputDone zostanie zamknięty:
script.js - disconnect()
// CODELAB: Close the output stream.
if (outputStream) {
await outputStream.getWriter().close();
await outputDone;
outputStream = null;
outputDone = null;
}
Na koniec zamknij port szeregowy i poczekaj, aż zostanie zamknięty:
script.js - disconnect()
// CODELAB: Close the port.
await port.close();
port = null;
Wypróbuj
Teraz możesz otwierać i zamykać port szeregowy w dowolnym momencie.
- Odśwież stronę.
- Kliknij przycisk Połącz.
- W oknie wyboru portu szeregowego wybierz urządzenie BBC micro:bit i kliknij Połącz.
- Na matrycy LED micro:bit powinien pojawić się uśmiech.
- Naciśnij przycisk Odłącz i sprawdź, czy matryca LED wyłącza się i czy w konsoli nie ma błędów.
9. Gratulacje
Gratulacje! Udało Ci się utworzyć pierwszą aplikację internetową, która korzysta z interfejsu Web Serial API.
Najnowsze informacje o interfejsie Web Serial API i innych nowych, ciekawych funkcjach internetowych, nad którymi pracuje zespół Chrome, znajdziesz na stronie https://goo.gle/fugu-api-tracker.