Introduzione all'API Web Serial

1. Introduzione

Ultimo aggiornamento: 19/09/2022

Cosa creerai

In questo codelab, creerai una pagina web che utilizza l'API Web Serial per interagire con una scheda BBC micro:bit per mostrare le immagini sulla relativa matrice LED 5x5. Scoprirai l'API Web Serial e come utilizzare stream leggibili, scrivibili e di trasformazione per comunicare con i dispositivi seriali tramite il browser.

67543f4caaaca5de.png

Cosa imparerai a fare

  • Come aprire e chiudere una porta seriale web
  • Come utilizzare un loop di lettura per gestire i dati da un flusso di input
  • Come inviare i dati tramite un flusso di scrittura

Che cosa ti serve

Abbiamo scelto di utilizzare micro:bit v1 per questo codelab perché è conveniente, offre alcuni ingressi (pulsanti) e uscite (display LED 5x5) e può fornire ingressi e uscite aggiuntivi. Consulta la pagina BBC micro:bit sul sito di Espruino per informazioni dettagliate sulle funzionalità di micro:bit.

2. Informazioni sull'API Web Serial

L'API Web Serial consente ai siti web di leggere e scrivere su un dispositivo seriale con script. L'API collega il web e il mondo fisico consentendo ai siti web di comunicare con dispositivi seriali, come microcontroller e stampanti 3D.

Esistono molti esempi di software di controllo realizzati con la tecnologia web. Ad esempio:

In alcuni casi, questi siti web comunicano con il dispositivo tramite un'applicazione agente nativa installata manualmente dall'utente. In altri casi, l'applicazione viene distribuita in un'applicazione nativa pacchettizzata tramite un framework come Electron. In altri casi, all'utente viene richiesto di eseguire un passaggio aggiuntivo, ad esempio la copia di un'applicazione compilata sul dispositivo con un'unità flash USB.

L'esperienza utente può essere migliorata fornendo una comunicazione diretta tra il sito e il dispositivo che controlla.

3. Preparazione

Ottieni il codice

Abbiamo inserito tutto ciò che ti serve per questo codelab in un progetto Glitch.

  1. Apri una nuova scheda del browser e vai alla pagina https://web-serial-codelab-start.glitch.me/.
  2. Fai clic sul link Remix Glitch per creare la tua versione del progetto iniziale.
  3. Fai clic sul pulsante Mostra, quindi scegli In una nuova finestra per vedere il tuo codice in azione.

4. Aprire una connessione seriale

Verificare se l'API Web Serial è supportata

La prima cosa da fare è verificare se l'API Web Serial è supportata nel browser corrente. Per farlo, controlla se serial è in navigator.

Nell'evento DOMContentLoaded, aggiungi il seguente codice al progetto:

script.js - DOMContentLoaded

// CODELAB: Add feature detection here.
const notSupported = document.getElementById('notSupported');
notSupported.classList.toggle('hidden', 'serial' in navigator);

Viene controllato se la funzionalità Web Serial è supportata. In caso affermativo, questo codice nasconde il banner che indica che il numero di serie web non è supportato.

Prova

  1. Carica la pagina.
  2. Verifica che nella pagina non sia visualizzato un banner rosso che indica che il numero di serie web non è supportato.

Apri la porta seriale

A questo punto, dobbiamo aprire la porta seriale. Come la maggior parte delle altre API moderne, l'API Web Serial è asincrona. Questo impedisce alla UI di bloccarsi quando è in attesa di input, ma è anche importante perché la pagina web potrebbe ricevere dati seriali in qualsiasi momento e abbiamo bisogno di un modo per ascoltarli.

Poiché un computer può avere più dispositivi seriali, quando il browser tenta di richiedere una porta, chiede all'utente di scegliere il dispositivo a cui connettersi.

Aggiungi il codice seguente al progetto:

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

La chiamata di requestPort chiede all'utente a quale dispositivo vuole connettersi. La chiamata a port.open apre la porta. Dobbiamo anche indicare la velocità di comunicazione con il dispositivo seriale. Il micro:bit della BBC utilizza una connessione a 9600 baud tra il chip da USB a seriale e il processore principale.

Colleghiamo anche il pulsante di connessione e facciamo in modo che chiami connect() quando l'utente lo fa clic.

Aggiungi il seguente codice al progetto:

script.js - clickConnect()

// CODELAB: Add connect code here.
await connect();

Prova

Il nostro progetto ora ha il minimo indispensabile per iniziare. Quando fa clic sul pulsante Connetti, all'utente viene chiesto di selezionare il dispositivo seriale a cui connettersi e di stabilire una connessione al micro:bit.

  1. Ricarica la pagina.
  2. Fai clic sul pulsante Connetti.
  3. Nella finestra di dialogo di scelta della porta seriale, seleziona il dispositivo BBC micro:bit e fai clic su Connetti.
  4. Nella scheda dovresti vedere un'icona che indica il collegamento a un dispositivo seriale:

e695daf2277cd3a2.png

Configurare uno stream di input per ascoltare i dati dalla porta seriale

Dopo aver stabilito la connessione, dobbiamo impostare uno stream di input e un lettore per leggere i dati dal dispositivo. Per prima cosa, riceveremo lo stream leggibile dalla porta chiamando port.readable. Poiché sappiamo che riceveremo il testo dal dispositivo, lo invieremo tramite un decodificatore di testo. Successivamente, acquisiremo un lettore e inizieremo il ciclo di lettura.

Aggiungi il seguente codice al progetto:

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

Il loop di lettura è una funzione asincrona che viene eseguita in un loop e attende i contenuti senza bloccare il thread principale. Quando arrivano nuovi dati, il lettore restituisce due proprietà: value e un valore booleano done. Se done è impostato su true, la porta è stata chiusa o non sono disponibili altri dati.

Aggiungi il seguente codice al progetto:

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

Prova

Il nostro progetto può ora connettersi al dispositivo e aggiungerà all'elemento di log eventuali dati ricevuti dal dispositivo.

  1. Ricarica la pagina.
  2. Fai clic sul pulsante Connetti.
  3. Nella finestra di dialogo di scelta della porta seriale, seleziona il dispositivo BBC micro:bit e fai clic su Connetti.
  4. Dovresti vedere il logo di Espruino:

dd52b5c37fc4b393.png

Configurare uno stream di output per inviare dati alla porta seriale

La comunicazione seriale è generalmente bidirezionale. Oltre a ricevere dati dalla porta seriale, vogliamo anche inviare dati alla porta. Come per lo stream di input, invieremo solo testo tramite lo stream di output a micro:bit.

Innanzitutto, crea uno stream del codificatore di testo e invialo a 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;

Se collegata tramite seriale con il firmware Espruino, la scheda BBC micro:bit agisce come loop di lettura, valutazione e stampa (REPL) JavaScript, in modo simile a quanto si ottiene in una shell Node.js. A questo punto, dobbiamo fornire un metodo per inviare i dati allo stream. Il codice seguente ottiene un writer dallo stream di output, quindi utilizza write per inviare ogni riga. Ogni riga inviata include un carattere di a capo (\n) per indicare a micro:bit di valutare il comando inviato.

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

Per mettere il sistema in uno stato noto e impedire che restituisca i caratteri che gli inviamo, dobbiamo inviare CTRL-C e disattivare l'eco.

script.js - connect()

// CODELAB: Send CTRL-C and turn off echo on REPL
writeToStream('\x03', 'echo(false);');

Prova

Il nostro progetto è ora in grado di inviare e ricevere dati dal micro:bit. Verifichiamo di poter inviare correttamente un comando:

  1. Ricarica la pagina.
  2. Fai clic sul pulsante Connetti.
  3. Nella finestra di dialogo di scelta della porta seriale, seleziona il dispositivo BBC micro:bit e fai clic su Connetti.
  4. Apri la scheda Console in Chrome DevTools e digita writeToStream('console.log("yes")');.

Sulla pagina dovrebbe essere stampato qualcosa di simile a questo:

15e2df0064b5de28.png

5. Controlla la matrice LED

Crea la stringa della griglia della matrice

Per controllare la matrice LED sul micro:bit, dobbiamo chiamare show(). Questo metodo mostra la grafica sullo schermo LED 5 x 5 integrato. Questa operazione accetta un numero binario o una stringa.

Eseguiremo l'iterazione sulle caselle di controllo e genereremo un array di 1 e 0 per indicare quale opzione è selezionata e quale no. Poi dobbiamo invertire l'array, perché l'ordine delle caselle di controllo è l'opposto dell'ordine dei LED nella matrice. Successivamente, convertiamo l'array in una stringa e creiamo il comando da inviare a micro: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('')})`);

Collegare le caselle di controllo per aggiornare la matrice

Successivamente, dobbiamo rilevare le modifiche alle caselle di controllo e, se cambiano, inviare queste informazioni a micro:bit. Nel codice di rilevamento delle funzionalità (// CODELAB: Add feature detection here.), aggiungi la seguente riga:

script.js - DOMContentLoaded

initCheckboxes();

Ripristinamo anche la griglia quando micro:bit viene connesso per la prima volta, in modo che mostri una faccina felice. La funzione drawGrid() è già fornita. Questa funzione funziona in modo simile a sendGrid(): prende un array di 1 e 0 e seleziona le caselle di controllo in base alle esigenze.

script.js - clickConnect()

// CODELAB: Reset the grid on connect here.
drawGrid(GRID_HAPPY);
sendGrid();

Prova

Ora, quando la pagina apre una connessione al micro:bit, invierà una faccina felice. Fai clic sulle caselle di controllo per aggiornare il display della matrice LED.

  1. Ricarica la pagina.
  2. Fai clic sul pulsante Connetti.
  3. Nella finestra di dialogo di scelta della porta seriale, seleziona il dispositivo BBC micro:bit e fai clic su Connetti.
  4. Dovresti vedere un sorriso sulla matrice LED di micro:bit.
  5. Disegna un motivo diverso sulla matrice LED modificando le caselle di controllo.

6. Collega i pulsanti di micro:bit

Aggiungere un evento di orologio sui pulsanti di micro:bit

Micro:bit ha due pulsanti, uno su ciascun lato della matrice LED. Espruino fornisce una funzione setWatch che invia un evento/callback quando viene premuto il pulsante. Poiché vogliamo ascoltare entrambi i pulsanti, creeremo la funzione generica e stamperemo i dettagli dell'evento.

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

Successivamente, dobbiamo collegare entrambi i pulsanti (denominati BTN1 e BTN2 sulla scheda micro:bit) ogni volta che la porta seriale è collegata al dispositivo.

script.js - clickConnect()

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

Prova

Oltre a mostrare una faccina felice quando il dispositivo è connesso, premendo uno dei due pulsanti del micro:bit verrà aggiunto alla pagina del testo che indica quale pulsante è stato premuto. Molto probabilmente, ogni carattere si troverà su una riga separata.

  1. Ricarica la pagina.
  2. Fai clic sul pulsante Connetti.
  3. Nella finestra di dialogo di scelta della porta seriale, seleziona il dispositivo BBC micro:bit e fai clic su Connetti.
  4. Dovresti vedere un sorriso sulla matrice LED di micro:bit.
  5. Premi i pulsanti sul micro:bit e verifica che questo aggiunga nuovo testo alla pagina con i dettagli del pulsante premuto.

7. Utilizza un flusso di trasformazione per analizzare i dati in entrata

Gestione di base dello streaming

Quando viene premuto uno dei pulsanti micro:bit, il micro:bit invia i dati alla porta seriale attraverso uno stream. I flussi sono molto utili, ma possono anche rappresentare una sfida perché non riceverai necessariamente tutti i dati in una volta sola, ma potrebbero essere suddivisi arbitrariamente.

Al momento l'app stampa lo stream in arrivo non appena arriva (in readLoop). Nella maggior parte dei casi, ogni carattere si trova su una riga separata, il che però non è molto utile. Idealmente, lo stream deve essere analizzato in singole righe e ogni messaggio deve essere mostrato come riga separata.

Trasformazione dei flussi con TransformStream

Per farlo, possiamo utilizzare un flusso di trasformazione ( TransformStream), che consente di analizzare il flusso in entrata e restituire i dati analizzati. Un flusso di trasformazione può trovarsi tra l'origine del flusso (in questo caso, micro:bit) e ciò che lo utilizza (in questo caso readLoop) e può applicare una trasformazione arbitraria prima di essere consumata. Considerala come una catena di montaggio: poiché un widget scende lungo la linea, ogni passaggio modifica il widget in modo che, quando arriva alla destinazione finale, sia un widget completamente funzionante.

Per ulteriori informazioni, consulta i concetti dell'API Streams di MDN.

Trasforma lo stream con LineBreakTransformer

Creiamo una classe LineBreakTransformer che riceve uno stream e lo suddivide in base ai ritorni a capo (\r\n). La classe ha bisogno di due metodi, transform e flush. Il metodo transform viene chiamato ogni volta che lo stream riceve nuovi dati. Può mettere in coda i dati o salvarli per un utilizzo futuro. Il metodo flush viene chiamato quando lo stream viene chiuso e gestisce tutti i dati che non sono stati ancora elaborati.

Nel nostro metodo transform, aggiungeremo nuovi dati a container e poi verificheremo se sono presenti interruzioni di riga in container. Se sono presenti, dividilo in un array, quindi ripeti l'iterazione tra le righe, chiamando controller.enqueue() per inviare le righe analizzate.

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

Quando lo stream viene chiuso, elimineremo semplicemente i dati rimanenti nel contenitore utilizzando enqueue.

script.js - LineBreakTransformer.flush()

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

Infine, dobbiamo incanalare lo stream in entrata attraverso il nuovo LineBreakTransformer. Il nostro stream di input originale è stato trasmesso solo tramite un TextDecoderStream, quindi dobbiamo aggiungere un altro pipeThrough per trasmetterlo attraverso il nostro nuovo 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()));

Prova

Ora, quando premi uno dei pulsanti di micro:bit, i dati stampati dovrebbero essere restituiti su una singola riga.

  1. Ricarica la pagina.
  2. Fai clic sul pulsante Connetti.
  3. Nella finestra di dialogo di scelta della porta seriale, seleziona il dispositivo BBC micro:bit e fai clic su Connetti.
  4. Dovresti vedere un sorriso sulla matrice LED di micro:bit.
  5. Premi i pulsanti sul micro:bit e verifica che venga visualizzato qualcosa di simile al seguente:

eead3553d29ee581.png

Trasforma lo stream con JSONTransformer

Potremmo provare ad analizzare la stringa in JSON in readLoop, ma invece, creiamo un trasformatore JSON molto semplice che trasformerà i dati in un oggetto JSON. Se i dati non sono JSON validi, restituisci semplicemente i dati ricevuti.

script.js - JSONTransformer.transform

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

Successivamente, indirizza il flusso attraverso JSONTransformer, dopo che è passato attraverso LineBreakTransformer. In questo modo possiamo mantenere semplice il nostro JSONTransformer, poiché sappiamo che il JSON verrà sempre inviato su una singola riga.

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

Prova

Ora, quando premi uno dei pulsanti micro:bit, dovresti vedere [object Object] stampato sulla pagina.

  1. Ricarica la pagina.
  2. Fai clic sul pulsante Connetti.
  3. Nella finestra di dialogo di scelta della porta seriale, seleziona il dispositivo BBC micro:bit e fai clic su Connetti.
  4. Dovresti vedere un sorriso sulla matrice LED di micro:bit.
  5. Premi i pulsanti sul micro:bit e verifica che venga visualizzato qualcosa di simile al seguente:

Risposta alla pressione dei pulsanti

Per rispondere alle pressioni dei pulsanti di micro:bit, aggiorna readLoop per verificare se i dati ricevuti sono un object con una proprietà button. Quindi, chiama buttonPushed per gestire l'attivazione del pulsante.

script.js - readLoop()

const { value, done } = await reader.read();
if (value && value.button) {
  buttonPushed(value);
} else {
  log.textContent += value + '\n';
}

Quando viene premuto un pulsante micro:bit, il display della matrice LED dovrebbe cambiare. Utilizza il seguente codice per impostare la matrice:

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

Prova

Ora, quando premi uno dei pulsanti micro:bit, la matrice LED dovrebbe cambiare in una faccina felice o una faccina triste.

  1. Ricarica la pagina.
  2. Fai clic sul pulsante Connetti.
  3. Nella finestra di dialogo di scelta della porta seriale, seleziona il dispositivo BBC micro:bit e fai clic su Connetti.
  4. Nella matrice LED di micro:bit dovresti vedere un sorriso.
  5. Premi i pulsanti su micro:bit e verifica che la matrice LED cambi.

8. Chiusura della porta seriale

Il passaggio finale consiste nel collegare la funzionalità di disconnessione per chiudere la porta al termine dell'operazione dell'utente.

Chiudi la porta quando l'utente fa clic sul pulsante Connetti/Scollega

Quando l'utente fa clic sul pulsante Connetti/Disconnetti, dobbiamo chiudere la connessione. Se la porta è già aperta, chiama disconnect() e aggiorna la UI per indicare che la pagina non è più connessa al dispositivo seriale.

script.js - clickConnect()

// CODELAB: Add disconnect code here.
if (port) {
  await disconnect();
  toggleUIConnected(false);
  return;
}

Chiudi i flussi e la porta

Nella funzione disconnect, dobbiamo chiudere il flusso di input e quello di output, quindi chiudere la porta. Per chiudere lo stream di input, chiama reader.cancel(). La chiamata a cancel è asincrona, quindi dobbiamo utilizzare await per attendere il completamento:

script.js - disconnect()

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

Per chiudere il flusso di output, recupera un writer, chiama close() e attendi la chiusura dell'oggetto outputDone:

script.js - disconnect()

// CODELAB: Close the output stream.
if (outputStream) {
  await outputStream.getWriter().close();
  await outputDone;
  outputStream = null;
  outputDone = null;
}

Infine, chiudi la porta seriale e attendi che si chiuda:

script.js - disconnect()

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

Prova

Ora puoi aprire e chiudere la porta seriale a tuo piacimento.

  1. Ricarica la pagina.
  2. Fai clic sul pulsante Connetti.
  3. Nella finestra di dialogo del selettore della porta seriale, seleziona il dispositivo micro:bit della BBC e fai clic su Connetti.
  4. Dovresti vedere un sorriso sulla matrice LED di micro:bit
  5. Premi il pulsante Disconnetti e verifica che la matrice LED si spenga e che non siano presenti errori nella console.

9. Complimenti

Complimenti! Hai creato la tua prima app web che utilizza l'API Web Serial.

Visita la pagina https://goo.gle/fugu-api-tracker per scoprire le ultime novità sull'API Web Serial e su tutte le altre nuove funzionalità web su cui il team di Chrome sta lavorando.