Wprowadzenie do interfejsu Web Serial API

Ostatnia aktualizacja: 11 października 2019 r.

Co stworzysz

Dzięki nim dowiesz się, jak utworzyć stronę internetową, która korzysta z interfejsu Web Serial API do interakcji z płytką BBC micro:bit, aby wyświetlać obrazy na matrycy LED 5 x 5. Poznasz też interfejs Web Serial API oraz dowiesz się, jak korzystać z czytelnych, zapisanych i transformowanych strumieni do komunikacji z urządzeniami szeregowymi przez przeglądarkę.

Czego się nauczysz:

  • Otwieranie i zamykanie portu szeregowego aplikacji internetowej
  • Używanie pętli odczytu do obsługi danych ze strumienia wejściowego
  • Jak wysyłać dane przez strumień zapisu

Czego potrzebujesz

Na potrzeby tego ćwiczenia z ćwiczenia z programowania postanowiliśmy skorzystać z mikro:bitu, ponieważ jest ono przystępne cenowo i oferuje kilka opcji wejścia (przycisków) i wyjściowych (wyświetlacz LED 5 x 5), a także udostępnia dodatkowe dane wejściowe. Więcej informacji na temat możliwości obsługi mikro:bitu znajdziesz na stronie BBC mikro:bit w witrynie Espruino.

Interfejs Web Serial API umożliwia witrynom odczytywanie i zapisywanie stron szeregowych za pomocą skryptów. Interfejs API łączy internet z siecią fizyczną, umożliwiając stronom komunikowanie się z urządzeniami szeregowymi, takimi jak mikrokontrolery i drukarki 3D.

Istnieje wiele przykładów oprogramowania sterującego opracowanych przy użyciu technologii internetowej. Przykład:

W niektórych przypadkach te strony komunikują się z urządzeniem przy użyciu natywnej aplikacji agenta, która jest instalowana ręcznie przez użytkownika. W innych przypadkach aplikacja jest dostarczana w aplikacji natywnej w pakiecie za pomocą platformy takiej jak Electron. W innych przypadkach użytkownik musi wykonać dodatkowy krok, na przykład skopiować skompilowaną aplikację na urządzenie, używając dysku flash USB.

Wrażenia użytkownika można poprawić, udostępniając bezpośrednią komunikację między witryną a urządzeniem, którym steruje.

Włącz interfejs Web Serial API

Program Web Serial API jest obecnie w fazie opracowywania i jest dostępny tylko pod flagą. Musisz włączyć flagę #enable-experimental-web-platform-features w narzędziu chrome://flags.

Pobierz kod

Wszystkie potrzebne elementy ćwiczenia znajdziesz w projekcie Glitch.

  1. Otwórz nową kartę przeglądarki i wejdź na https://web-serial-codelab-start.glitch.me/.
  2. Kliknij link Remiks Glitch, aby utworzyć własną wersję projektu startowego.
  3. Kliknij przycisk Pokaż, a następnie wybierz W nowym oknie, aby zobaczyć, jak działa kod.

Sprawdź, 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 język serial jest w navigator.

W zdarzeniu DOMContentLoaded dodaj do projektu ten kod:

script.js - DOMContentLoaded

// CODELAB: Add feature detection here.
if ('serial' in navigator) {
  const notSupported = document.getElementById('notSupported');
  notSupported.classList.add('hidden');
}

To sprawdza, czy serial internetowy jest obsługiwany. Jeśli tak, kod ukrywa baner informujący o tym, że numer seryjny 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.

Otwieranie portu szeregowego

Następnie trzeba otworzyć port szeregowy. Podobnie jak większość innych 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 otrzymywać w każdej chwili dane szeregowe. Potrzebujemy sposobu na ich wykrycie.

Komputer może mieć wiele urządzeń szeregowych, więc gdy przeglądarka próbuje przesłać żądanie przeniesienia, wyświetla użytkownikowi 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 informuje użytkownika, którego urządzenia chce użyć. Wywołanie usługi port.open otwiera port. Musimy też określić szybkość, z jaką chcemy komunikować się z urządzeniem szeregowym. Mikrodane BBC:bit 9600 łączy procesor USB-serial z procesorem głównym.

Połączy się także przycisk połączenia i wywoła metodę 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 bardzo drobny minimum, aby rozpocząć pracę. Po kliknięciu przycisku Połącz użytkownik zobaczy prośbę o wybranie urządzenia szeregowego, z którym ma zostać nawiązane połączenie, a następnie nawiązanie połączenia z mikro:bitem.

  1. Odśwież stronę.
  2. Kliknij przycisk Połącz.
  3. W oknie wyboru portów szeregowych wybierz urządzenie micro:bit BBC i kliknij Połącz.
  4. Na karcie powinna być widoczna ikona wskazująca połączenie z urządzeniem szeregowym:

Skonfiguruj strumień wejściowy tak, aby nasłuchiwał danych z portu szeregowego

Po nawiązaniu połączenia musimy skonfigurować strumień danych wejściowych i czytnik, aby odczytywać dane z urządzenia. Najpierw pobieramy czytelny strumień z portu, wywołując port.readable. Skoro wiemy, że pobieramy tekst z urządzenia, sprawdzimy jakość dekodera tekstu. Odczytujemy teraz czytnik i zapętlamy pętlę.

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 asynchroniczny, która działa w pętli i czeka na treść bez blokowania głównego wątku. Po otrzymaniu nowych danych czytnik zwraca 2 właściwości: logikę value i wartość done. Jeśli done ma wartość true (prawda), port został zamknięty lub nie są już pobierane ż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ć wszystkie dane otrzymane z urządzenia do elementu dziennika.

  1. Odśwież stronę.
  2. Kliknij przycisk Połącz.
  3. W oknie wyboru portów szeregowych wybierz urządzenie micro:bit BBC i kliknij Połącz.
  4. Powinno się pojawić logo Espruino:

Konfigurowanie strumienia wyjściowego do wysyłania danych do portu szeregowego

Komunikacja szeregowa jest zazwyczaj dwukierunkowa. Oprócz odbierania danych z portu szeregowego chcemy również wysłać dane do tego portu. Podobnie jak w przypadku strumienia wejściowego będziemy wysyłać tekst przez ten strumień do mikro:bita.

Najpierw utwórz strumień kodera i przepłyń 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;

Jeśli podłączysz urządzenie szeregowo do oprogramowania Espruino, płyta BBC: Następnie musimy udostępnić metodę przesyłania danych do strumienia. Poniższy kod pobiera zapis z strumienia wyjściowego, a następnie używa write do wysyłania każdego wiersza. Każdy wysłany wiersz zawiera znak nowego wiersza (\n), który informuje mikro:bit o ocenie 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 uniemożliwić wysyłanie kolejnych znaków, które wysyłamy, 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 mikro:bit. Sprawdź, czy możemy prawidłowo wysłać polecenie:

  1. Odśwież stronę.
  2. Kliknij przycisk Połącz.
  3. W oknie wyboru portów szeregowych wybierz urządzenie micro:bit BBC i kliknij Połącz.
  4. Otwórz kartę Konsola w Narzędziach deweloperskich w Chrome i wpisz
    writeToStream('console.log("yes")');

Powinno być widoczne na stronie coś takiego:

Tworzenie ciągu znaków matrycy

Aby sterować matrycą LED w mikro:bit, musisz wywołać show(). Ta metoda pokazuje grafikę na wbudowanym ekranie 5 x 5. Wymaga to podania liczby binarnej lub ciągu tekstowego.

Będziemy iterować pola wyboru, aby wygenerować tablicę z 1 i 0 wskazującą, która opcja jest zaznaczona, a która nie. Następnie musimy odwrócić tablicę, ponieważ pola wyboru są przeciwne do kolejności diod LED w matrycy. Następnie konwertujemy tablicę na ciąg znaków i utworzyliśmy polecenie wysłania do mikro:bit.

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 – jeśli się zmienią – wysłać te informacje do mikro:bit. W kodzie wykrywania cech (// CODELAB: Add feature detection here.) dodaj ten wiersz:

script.js - DOMContentLoaded

initCheckboxes();

Zresetujmy też siatkę po pierwszym połączeniu mikro:bitu, by pokazała szczęśliwą twarz. Funkcja drawGrid() jest już dostępna. Ta funkcja działa podobnie jak funkcja sendGrid(). Przyjmuje tablicę 1 s i 0 s 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, wywoła szczęśliwą twarz. Kliknięcie pól wyboru zaktualizuje wyświetlacz w matrycy LED.

  1. Odśwież stronę.
  2. Kliknij przycisk Połącz.
  3. W oknie wyboru portów szeregowych wybierz urządzenie micro:bit BBC i kliknij Połącz.
  4. Na tablicy LED mikro:bit powinna pojawić się uśmiech.
  5. Narysuj inny wzorzec na matrycy LED, zmieniając pola wyboru.

Dodawanie zdarzenia oglądania za pomocą przycisków micro:bit

Na mikro:bitu są 2 przyciski – jeden z obu stron matrycy LED. Espruino udostępnia funkcję setWatch, która wysyła zdarzenie/wywołanie zwrotne po naciśnięciu przycisku. Chcemy słuchać obu przycisków, dlatego staramy się, aby nasza funkcja była ogólna, a druki mogli ją szczegółowo wydrukować.

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 przyłączać oba przyciski (BTN1 i BTN2 na płytce micro:bit) za każdym razem, gdy podłączam port szeregowy do urządzenia.

script.js - clickConnect()

// CODELAB: Initialize micro:bit buttons.
watchButton('BTN1');
watchButton('BTN2');

Wypróbuj

Naciśnięcie jednego z przycisków mikro:bit nie tylko powoduje wyświetlenie szczęśliwej twarzy, ale też powoduje, że na stronie pojawia się tekst wskazujący, który przycisk został naciśnięty. Najprawdopodobniej każdy znak jest w osobnym wierszu.

  1. Odśwież stronę.
  2. Kliknij przycisk Połącz.
  3. W oknie wyboru portów szeregowych wybierz urządzenie micro:bit BBC i kliknij Połącz.
  4. Na tablicy LED mikro:bitów powinien być widoczny uśmiech.
  5. Naciśnij przyciski w mikro:bit i sprawdź, czy łączy nowy tekst ze stroną.

Podstawowa obsługa strumienia

Gdy jeden z przycisków mikro:bit zostanie przekazany, mikroserwis wysyła dane do portu szeregowego przez strumień. Strumienie są bardzo przydatne, ale mogą być wyzwaniem, ponieważ nie otrzymasz wszystkich danych naraz i mogą one zostać podzielone na fragmenty.

Aplikacja wydrukuje przychodzący strumień przychodzący (w readLoop). W większości przypadków każdy znak znajduje się w osobnym wierszu, ale nie jest to zbyt pomocne. W idealnym przypadku strumień powinien zostać przeanalizowany jako pojedynczy wiersz, a każda wiadomość powinna być przedstawiona w osobnym wierszu.

Przekształcanie strumieni za pomocą TransformStream

Możesz to zrobić za pomocą strumienia przekształcenia (TransformStream), który umożliwia analizowanie strumienia przychodzącego i zwracanie przeanalizowanych danych. Strumień przekształcania może znajdować się między źródłem strumienia (w tym przypadku mikro:bit) a tym, co go wykorzystuje (w tym przypadku readLoop), oraz może stosować dowolną transformację, zanim zostanie w końcu wykorzystana. Pomyśl o nim jak o linii produkcyjnej: w miarę, jak widżet przesuwa się w dół, każdy etap jego działania modyfikuje widżet w taki sposób, by z czasem okazał się w pełni funkcjonalnym widżetem.

Więcej informacji znajdziesz na stronie MDN&33's Streams API.

Przekształć strumień w LineBreakTransformer

Utwórzmy klasę LineBreakTransformer, która pobierze strumień i pofragmentuje ją na podstawie podziałów wierszy (\r\n). Klasa musi mieć dwie metody: transform i flush. Metoda transform jest wywoływana za każdym razem, gdy strumień otrzymuje nowe dane. Dane mogą zostać umieszczone w kolejce lub zapisane 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 przypadku metody transform dodamy nowe dane do aplikacji container, a następnie sprawdzimy, czy w wierszu container występują podziały wiersza. Jeśli tak, podziel go na tablicę, a następnie powtórz wiersze, wywołując controller.enqueue() w celu wysłania przeanalizowanych linii.

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));

Po zamknięciu strumienia pozostałe dane w kontenerze są usuwane przy użyciu enqueue.

script.js - LineBreakTransformer.flush()

// CODELAB: Flush the stream.
controller.enqueue(this.container);

Na koniec musimy przeprowadzić potok strumieniowy przez nowy LineBreakTransformer. Pierwotny strumień był przesyłany tylko przez TextDecoderStream, dlatego potrzebujemy dodatkowego pipeThrough, by przejść 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

Gdy naciśniesz jeden z przycisków mikro:bit, wydrukowane dane powinny być zwracane w jednym wierszu.

  1. Odśwież stronę.
  2. Kliknij przycisk Połącz.
  3. W oknie wyboru portów szeregowych wybierz urządzenie micro:bit BBC i kliknij Połącz.
  4. Na tablicy LED mikro:bit powinna pojawić się uśmiech.
  5. Naciśnij przyciski w mikro:bit i sprawdź, czy widzisz coś takiego:

Przekształć strumień w JSONTransformer

Można spróbować przeanalizować ten ciąg znaków w formacie JSON w readLoop, ale zamiast tego utwórzmy prosty transformer JSON, który przekształci dane w obiekt JSON. Jeśli dane nie są prawidłowymi ciągami JSON, po prostu zwróć otrzymane dane.

script.js - JSONTransformer.transform

// CODELAB: Attempt to parse JSON content
try {
  controller.enqueue(JSON.parse(chunk));
} catch (e) {
  controller.enqueue(chunk);
}

Potem potok płynie przez JSONTransformer, gdy przejdzie przez LineBreakTransformer. Dzięki temu mamy pewność, że plik JSONTransformer będzie prosty, ponieważ nigdy w żadnym wierszu nie będą przesyłane żadne dane w formacie JSON.

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 naciśniesz jeden z przycisków mikro:bit, na stronie powinna pojawić się wartość [object Object].

  1. Odśwież stronę.
  2. Kliknij przycisk Połącz.
  3. W oknie wyboru portów szeregowych wybierz urządzenie micro:bit BBC i kliknij Połącz.
  4. Na tablicy LED mikro:bit powinna pojawić się uśmiech.
  5. Kliknij przyciski mikro:bit i sprawdź, czy widzisz coś takiego:

Reagowanie na naciśnięcia przycisków

Aby odpowiadać na naciśnięcie przycisku micro:bit, zaktualizuj pole readLoop, aby sprawdzić, czy otrzymane dane to właściwość object z właściwością button. Następnie wywołaj przycisk buttonPushed, aby przekazać dane.

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:bit powinien się zmienić sposób wyświetlania wyświetlacza LED. Aby ustawić matrycę, 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

Gdy naciśniesz jeden z przycisków mikro:bit, tablica LED powinna zmienić się w szczęśliwą lub smutną twarz.

  1. Odśwież stronę.
  2. Kliknij przycisk Połącz.
  3. W oknie wyboru portów szeregowych wybierz urządzenie micro:bit BBC i kliknij Połącz.
  4. Na tablicy LED mikro:bitów powinien być widoczny uśmiech.
  5. Naciśnij przyciski na mikro:bitie i sprawdź, czy obudowa macierzy LED się zmieniła.

Ostatnim krokiem jest podłączenie portu i zakończenie połączenia, gdy użytkownik zakończy połączenie.

Zamykanie portu, gdy użytkownik kliknie przycisk Connect/Rozłącz

Gdy użytkownik kliknie przycisk Połącz/Odłącz, musimy zamknąć połączenie. Jeśli port jest już otwarty, wywołaj disconnect() i zaktualizuj interfejs, by 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, zamknąć strumień wyjściowy i zamknąć port. Aby zamknąć strumień wejściowy, zadzwoń pod numer reader.cancel(). Wywołanie cancel jest asynchroniczne, więc aby poczekać na jego zakończenie, musimy użyć tagu await:

script.js - disconnect()

// CODELAB: Close the input stream (reader).
if (reader) {
  await reader.cancel();
  await inputDone;
  reader = null;
  inputDone = null;
}

Aby zamknąć strumień, 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 zaczekaj, aż się zamknie:

script.js - disconnect()

// CODELAB: Close the port.
await port.close();
port = null;

Wypróbuj

Teraz możesz w dowolnej chwili otwierać i zamykać port szeregowy.

  1. Odśwież stronę.
  2. Kliknij przycisk Połącz.
  3. W oknie wyboru portów szeregowych wybierz urządzenie micro:bit BBC i kliknij Połącz.
  4. Na tablicy LED mikro:bit powinien pojawić się uśmiech
  5. Naciśnij przycisk Odłącz i sprawdź, czy matryca LED się wyłącza i czy w konsoli nie ma błędów.

Gratulacje! Udało Ci się utworzyć pierwszą aplikację internetową, która korzysta z interfejsu Web Serial API.

Zaglądaj na stronę https://goo.gle/fugu-api-tracker, gdzie znajdziesz najnowsze informacje o interfejsie Web Serial API i innych ciekawych funkcjach internetowych, nad którymi pracuje zespół Chrome.