Pierwsze kroki z interfejsem Web Serial API

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.

81167ab7c01d353d.png

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

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.

  1. Otwórz nową kartę przeglądarki i wejdź na stronę https://web-serial-codelab-start.glitch.me/.
  2. Kliknij link Remix Glitch, aby utworzyć własną wersję projektu początkowego.
  3. 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

  1. Załaduj stronę.
  2. 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.

  1. Odśwież stronę.
  2. Kliknij przycisk Połącz.
  3. W oknie wyboru portu szeregowego wybierz urządzenie BBC micro:bit i kliknij Połącz.
  4. Na karcie powinna być widoczna ikona wskazująca, że połączono się z urządzeniem szeregowym:

d9d0d3966960aeab.png

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.

  1. Odśwież stronę.
  2. Kliknij przycisk Połącz.
  3. W oknie wyboru portu szeregowego wybierz urządzenie BBC micro:bit i kliknij Połącz.
  4. Powinno się wyświetlić logo Espruino:

93494fd58ea835eb.png

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:

  1. Odśwież stronę.
  2. Kliknij przycisk Połącz.
  3. W oknie wyboru portu szeregowego wybierz urządzenie BBC micro:bit i kliknij Połącz.
  4. 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:

a13187e7e6260f7f.png

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.

  1. Odśwież stronę.
  2. Kliknij przycisk Połącz.
  3. W oknie wyboru portu szeregowego wybierz urządzenie BBC micro:bit i kliknij Połącz.
  4. Na matrycy LED micro:bit powinien pojawić się uśmiech.
  5. 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.

  1. Odśwież stronę.
  2. Kliknij przycisk Połącz.
  3. W oknie wyboru portu szeregowego wybierz urządzenie BBC micro:bit i kliknij Połącz.
  4. Na matrycy LED micro:bit powinien pojawić się uśmiech.
  5. 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: transformflush. 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.

  1. Odśwież stronę.
  2. Kliknij przycisk Połącz.
  3. W oknie wyboru portu szeregowego wybierz urządzenie BBC micro:bit i kliknij Połącz.
  4. Na matrycy LED micro:bit powinien pojawić się uśmiech.
  5. Naciśnij przyciski na micro:bicie i sprawdź, czy widzisz coś podobnego do tego:

6c2193880c748412.png

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].

  1. Odśwież stronę.
  2. Kliknij przycisk Połącz.
  3. W oknie wyboru portu szeregowego wybierz urządzenie BBC micro:bit i kliknij Połącz.
  4. Na matrycy LED micro:bit powinien pojawić się uśmiech.
  5. 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ę.

  1. Odśwież stronę.
  2. Kliknij przycisk Połącz.
  3. W oknie wyboru portu szeregowego wybierz urządzenie BBC micro:bit i kliknij Połącz.
  4. Na matrycy LED micro:bit powinien pojawić się uśmiech.
  5. 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.

  1. Odśwież stronę.
  2. Kliknij przycisk Połącz.
  3. W oknie wyboru portu szeregowego wybierz urządzenie BBC micro:bit i kliknij Połącz.
  4. Na matrycy LED micro:bit powinien pojawić się uśmiech.
  5. 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.