1. Wprowadzenie
Ostatnia aktualizacja: 19 września 2022 r.
Co utworzysz
W tym ćwiczeniu z programowania utworzysz stronę internetową, która korzysta z interfejsu Web Serial API, aby nawiązywać interakcje z płytką BBC micro:bit i wyświetlać na niej obrazy za pomocą matrycy LED 5 x 5. Dowiesz się więcej o interfejsie Web Serial API oraz o tym, jak używać strumieni do odczytu, zapisu i przekształcania, aby komunikować się z urządzeniami szeregowymi przez przeglądarkę.
Czego się nauczysz
- Jak otworzyć i zamknąć internetowy port szeregowy
- 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 v1 z oprogramowaniem Espruino 2v04
- najnowsza wersja Chrome (80 lub nowsza),
- znajomości języków HTML, CSS, JavaScript i Narzędzi deweloperskich w Chrome;
W tym laboratorium programistycznym użyliśmy urządzenia micro:bit 1, ponieważ jest ono niedrogie, ma kilka wejść (przycisków) i wyjść (wyświetlacz LED 5 x 5) oraz umożliwia podłączenie dodatkowych wejść i wyjść. Szczegółowe informacje o możliwościach mikro:bita znajdziesz na stronie BBC micro:bit w witrynie Espruino.
2. Informacje o interfejsie Web Serial API
Interfejs Web Serial API umożliwia stronom internetowym odczytywanie i zapisywanie danych na urządzeniu szeregowym za pomocą skryptów. Ten interfejs API łączy internet ze światem fizycznym, umożliwiając witrynom komunikowanie się z urządzeniami szeregowymi, takimi jak mikrokontrolery i drukarki 3D.
Istnieje wiele przykładów oprogramowania sterującego tworzonego z wykorzystaniem technologii internetowej. Na przykład:
Czasem takie strony komunikują się z urządzeniem za pomocą natywnej aplikacji agenta, którą użytkownik instaluje ręcznie. W innych przypadkach aplikacja jest udostępniana w 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 flash USB.
Wrażenia użytkownika można poprawić, zapewniając bezpośrednią komunikację między witryną a urządzeniem, które kontroluje.
3. Konfiguracja
Pobierz kod
Wszystko, czego potrzebujesz do tego ćwiczenia, zostało umieszczone 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 startowego.
- Kliknij przycisk Pokaż i wybierz W nowym oknie, by zobaczyć, jak działa Twój kod.
4. Otwieranie połączenia szeregowego
Sprawdzanie, czy interfejs Web Serial API jest obsługiwany
Najpierw sprawdź, czy interfejs Web Serial API jest obsługiwany w bieżącej przeglądarce. Aby to zrobić, sprawdź, czy serial
znajduje się w navigator
.
W przypadku zdarzenia 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 numer seryjny jest obsługiwany. Jeśli tak, kod ten spowoduje ukrycie banera z informacją, że funkcja Web Serial nie jest obsługiwana.
Wypróbuj
- Wczytaj stronę.
- Sprawdź, czy na stronie nie wyświetla się czerwony baner z informacją, że numer seryjny nie jest obsługiwany.
Otwórz port szeregowy
Teraz musimy otworzyć port szeregowy. Podobnie jak większość nowoczesnych interfejsów API, interfejs Web Serial API jest asynchroniczny. Zapobiega to blokowaniu interfejsu podczas oczekiwania na dane wejściowe, ale jest też ważne, ponieważ strona internetowa może otrzymać dane szeregowe w dowolnym momencie, a my musimy mieć możliwość ich przechwycenia.
Ponieważ komputer może mieć kilka urządzeń szeregowych, gdy przeglądarka próbuje poprosić o port, prosi użytkownika 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 });
Podczas wywołania requestPort
użytkownik zostaje poproszony o wybranie urządzenia, z którym chce się połączyć. Połączenie z numerem port.open
otworzy port. Musimy też określić szybkość, z jaką chcemy komunikować się z urządzeniem szeregowym. BBC micro:bit korzysta z połączenia o szybkości 9600 budów między układem USB-serowym a głównym procesorem.
Podłączmy też przycisk łączenia, tak aby wywoływał on connect()
, gdy użytkownik go kliknie.
Dodaj do projektu ten kod:
script.js - clickConnect()
// CODELAB: Add connect code here.
await connect();
Wypróbuj
Nasz projekt ma teraz minimum niezbędne do rozpoczęcia. Po kliknięciu przycisku Połącz użytkownik może wybrać urządzenie szeregowe, z którym ma się połączyć, a następnie połą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 pojawić się ikona wskazująca, że nastąpiło połączenie z urządzeniem szeregowym:
Konfigurowanie strumienia danych wejściowych na potrzeby odbierania danych z portu szeregowego
Po nawiązaniu połączenia trzeba skonfigurować strumień wejściowy i czytnik do odczytywania danych z urządzenia. Najpierw otrzymamy odczytywalny strumień z portu, wywołując funkcję port.readable
. Wiemy, że otrzymasz SMS-a z urządzenia, dlatego przekażemy go za pomocą dekodera tekstu. Teraz weźmiemy czytnik i uruchomimy 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 zwróci 2 właściwości: value
i wartość logiczną done
. Jeśli done
ma wartość true (prawda), port został zamknięty lub nie napływają już ż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 dołączyć 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 pojawić się logo Espruino:
Konfigurowanie strumienia wyjściowego do wysyłania danych na port szeregowy
Komunikacja szeregowa zazwyczaj odbywa się 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.
Najpierw utwórz strumień kodera tekstu i prześlij 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 tablica mikro:bitowa BBC działa jak pętla do odczytu i ewaluacji druku (REPL) JavaScriptu, podobnie jak w powłoce Node.js. Następnie musimy udostępnić metodę wysyłania danych do strumienia. Poniższy kod pobiera obiekt do zapisu ze strumienia wyjściowego, a potem używa funkcji write
do wysyłania poszczególnych wierszy. Każdy wysyłany wiersz zawiera znak nowego wiersza (\n
), który informuje funkcję micro:bit o ocenę 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 uniknąć powtarzania wysyłanych przez nas znaków, musimy wysłać kombinację klawiszy 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 micro:bita. 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")');
.
Strona powinna wyglądać tak:
5. Steruj matrycą diod LED
Tworzenie ciągu siatki macierzy
Aby sterować matrycą LED na micro:bit, musimy wywołać funkcję show()
. Ta metoda wyświetla grafikę na wbudowanym ekranie LED 5 x 5. Ta funkcja przyjmuje liczbę binarną lub ciąg znaków.
Przeanalizujemy pola wyboru i wygenerujemy tablicę 1 i 0 wskazującą, które z nich jest zaznaczone, a które nie. Następnie musimy odwrócić tablicę, bo pola wyboru są odwrotne niż kolejność diod LED w macierzy. Następnie konwertujemy tablicę na ciąg znaków i tworzymy polecenie, które zostanie wysłane do mikrodanych.
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 wsłuchać się w zmiany w polach wyboru i, jeśli ulegną one zmianie, przesłać informacje do pola micro:bit. W kodzie wykrywania funkcji (// CODELAB: Add feature detection here.
) dodaj ten wiersz:
script.js - DOMContentLoaded
initCheckboxes();
Zresetujmy też siatkę po pierwszym połączeniu bitu micro:bit, aby wyświetlała wesołą buźkę. Funkcja drawGrid()
jest już dostępna. Ta funkcja działa podobnie do funkcji sendGrid()
; przyjmuje tablicę 1 i 0 oraz odpowiednio zaznacza pola wyboru.
script.js - clickConnect()
// CODELAB: Reset the grid on connect here.
drawGrid(GRID_HAPPY);
sendGrid();
Wypróbuj
Gdy strona otworzy połączenie z micro:bitem, wyśle uśmiechniętą buźkę. Kliknięcie pól wyboru spowoduje zaktualizowanie stanu 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.
- Zmieniając pola wyboru, narysuj inny wzór na macierze LED.
6. Podłączanie przycisków micro:bit
Dodawanie zdarzenia dotyczącego oglądania przycisków micro:bit
Na płytce micro:bit znajdują się 2 przyciski, po jednym po obu stronach matrycy LED. Espruino udostępnia funkcję setWatch
, która wysyła zdarzenie/wywołanie zwrotne po naciśnięciu przycisku. Ponieważ chcemy słuchać obu przycisków, utworzymy funkcję ogólną, która będzie drukować 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 (o nazwach BTN1 i BTN2 na płytce micro:bit) za każdym razem, gdy port szeregowy jest podłączony do urządzenia.
script.js - clickConnect()
// CODELAB: Initialize micro:bit buttons.
watchButton('BTN1');
watchButton('BTN2');
Wypróbuj
Po połączeniu na ekranie pojawi się uśmiechnięta buźka, a po naciśnięciu dowolnego przycisku na micro:bitcie doda się tekst informujący, który przycisk został naciśnięty. Najprawdopodobniej każdy znak 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 macierze LED micro:bits powinien pojawić się uśmiech.
- Naciśnij przyciski na micro:bit i sprawdź, czy do strony dołącza się nowy tekst z informacjami o nacisniętym przycisku.
7. Używanie strumienia przekształcenia do analizowania przychodzących danych
Podstawowe przetwarzanie strumienia
Po naciśnięciu jednego z przycisków micro:bit przycisk micro:bit wysyła dane do portu szeregowego przez strumień. Strumienie są bardzo przydatne, ale mogą też stanowić wyzwanie, ponieważ niekoniecznie otrzymasz wszystkie dane naraz i mogą one być losowo dzielone na części.
Aplikacja drukuje przychodzący strumień, gdy tylko przyjdzie (readLoop
). W większości przypadków każdy znak jest w osobnym wierszu, ale nie jest to zbyt pomocne. W idealnej sytuacji strumień powinien być analizowany na poszczególne wiersze, a każda wiadomość powinna być wyświetlana w osobnym wierszu.
Przekształcanie strumieni za pomocą TransformStream
W tym celu możemy użyć strumienia transformacji (TransformStream
), który umożliwia analizowanie przychodzącego strumienia i zwracanie przetworzonych danych. Strumień przekształcenia może znajdować się między źródłem (w tym przypadku mikro:bitem), a wszystkim, co pobiera strumień (w tym przypadku readLoop
), i może zastosować dowolne przekształcenie, zanim zostanie ono przetworzone. Pomyśl o nim jak o linii montażowej: każdy widżet w tym wierszu modyfikuje widżet w taki sposób, że zanim dotrze on do miejsca docelowego, stanie się on w pełni działającym widżetem.
Więcej informacji znajdziesz w artykule MDN o koncepcjach interfejsu Streams API.
Przekształcanie strumienia za pomocą narzędzia LineBreakTransformer
Utwórzmy klasę LineBreakTransformer
, która rozpocznie strumień i podzieli go na segmenty według podziałów wiersza (\r\n
). Klasa potrzebuje 2 metod: transform
i flush
. Metoda transform
jest wywoływana za każdym razem, gdy strumień odbiera nowe dane. Może ona umieścić dane w kole lub zapisać je na później. Metoda flush
jest wywoływana po zamknięciu strumienia i przetwarza wszystkie dane, które nie zostały jeszcze przetworzone.
W metodie transform
dodamy nowe dane do tabeli container
, a potem sprawdzimy, czy w tabeli container
występują jakiekolwiek znaki końca wiersza. Jeśli tak, podziel go na tablicę, a następnie iteruj kolejne wiersze, wywołując funkcję controller.enqueue()
w celu wysłania przeanalizowanych wierszy.
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 usuniemy wszystkie pozostałe dane w kontenerze za pomocą polecenia enqueue
.
script.js - LineBreakTransformer.flush()
// CODELAB: Flush the stream.
controller.enqueue(this.container);
Na koniec musimy przekierować strumień danych przez nowy LineBreakTransformer
. Nasz oryginalny strumień danych wejściowych był przesyłany tylko przez TextDecoderStream
, więc musimy dodać dodatkowy strumień pipeThrough
, aby przesłać go przez nowy strumień 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, dane powinny zostać zwrócone na 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:bita powinien pojawić się uśmiech.
- Naciśnij przyciski na mikro:bit i sprawdź, czy wyświetla się coś takiego:
Przekształcanie strumienia za pomocą narzędzia JSONTransformer
Możemy spróbować przeanalizować ciąg znaków w formie JSON w funkcji readLoop
, ale zamiast tego utwórzmy bardzo prosty przekształcacz JSON, który przekształci dane w obiekt JSON. Jeśli dane nie są prawidłowe w formacie JSON, zwróć po prostu te dane.
script.js - JSONTransformer.transform
// CODELAB: Attempt to parse JSON content
try {
controller.enqueue(JSON.parse(chunk));
} catch (e) {
controller.enqueue(chunk);
}
Następnie przeprowadź strumień przez JSONTransformer
, gdy przeszedł przez LineBreakTransformer
. Pozwala to zachować prostotę JSONTransformer
, ponieważ wiemy, że plik JSON zostanie kiedykolwiek wysłany tylko 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
Gdy teraz naciśniesz jeden z przycisków micro:bita, 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 kluczu micro:bit i sprawdź, czy wyświetla się coś takiego:
Reagowanie na naciśnięcia przycisków
Aby reagować na naciśnięcie przycisku micro:bit, zaktualizuj readLoop
, aby sprawdzić, czy otrzymane dane to object
z właściwością button
. Następnie wywołaj buttonPushed
, by 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 micro:bita ekran 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 macierze LED micro:bits powinien pojawić się uśmiech.
- Naciśnij przyciski na micro:bit i sprawdź, czy matryca LED zmienia się.
8. Zamknij port szeregowy
Ostatnim krokiem jest podłączenie funkcji odłączania, aby zamknąć port, gdy użytkownik zakończy pracę.
Zamykaj port, gdy użytkownik kliknie przycisk Połącz/Rozłącz
Gdy użytkownik kliknie przycisk Połącz/Rozłącz, musimy zamknąć połączenie. Jeśli port jest już otwarty, wywołaj funkcję 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ń danych wejściowych, wywołaj reader.cancel()
. Wywołanie funkcji cancel
jest asynchroniczne, więc musimy użyć polecenia 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 żądanie writer
, wywołaj close()
i zaczekaj na zamknięcie obiektu outputDone
:
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 na jego zamknięcie:
script.js - disconnect()
// CODELAB: Close the port.
await port.close();
port = null;
Wypróbuj
Teraz możesz otwierać i zamykać port szeregowy według uznania.
- 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 nie występują żadne błędy w konsoli.
9. Gratulacje
Gratulacje! Udało Ci się utworzyć pierwszą aplikację internetową, która korzysta z interfejsu Web Serial API.
Na stronie https://goo.gle/fugu-api-tracker znajdziesz najnowsze informacje o interfejsie Web Serial API i innych, ciekawych funkcjach internetowych, nad którymi pracuje zespół Chrome.