Introduzione all'API Web Serial

1. Introduzione

Ultimo aggiornamento: 2020-07-21

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 flussi leggibili, scrivibili e trasformabili per comunicare con i dispositivi seriali tramite il browser.

81167ab7c01d353d.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 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 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 che viene 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 il necessario per questo codelab in un progetto Glitch.

  1. Apri una nuova scheda del browser e vai a 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 tuo progetto:

script.js - DOMContentLoaded

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

Questa opzione consente di verificare se il numero di serie web è supportato. 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

Ora 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 potrebbe avere più dispositivi seriali, quando il browser tenta di richiedere una porta, all'utente viene chiesto 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 codice seguente 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 del selettore della porta seriale, seleziona il dispositivo micro:bit della BBC e fai clic su Connetti.
  4. Nella scheda dovresti vedere un'icona che indica il collegamento a un dispositivo seriale:

d9d0d3966960aeab.png

Configurare uno stream di input per ascoltare i dati provenienti 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. Ora prenderemo un lettore e avvieremo il loop di lettura.

Aggiungi il codice seguente 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 codice seguente 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 del selettore della porta seriale, seleziona il dispositivo micro:bit della BBC e fai clic su Connetti.
  4. Dovresti vedere il logo di Espruino:

93494fd58ea835eb.png

Configurare uno stream di output per inviare i dati alla porta seriale

La comunicazione seriale è generalmente bidirezionale. Oltre a ricevere i dati dalla porta seriale, vogliamo anche inviarli alla porta. Come con il flusso di input, invieremo il testo sopra lo stream di output solo al micro:bit.

Innanzitutto, crea uno stream del codificatore di testo e trasmettilo su port.writeable tramite pipe.

script.js - connect()

// CODELAB: Add code setup the output stream here.
const encoder = new TextEncoderStream();
outputDone = encoder.readable.pipeTo(port.writable);
outputStream = encoder.writable;

Quando è connessa tramite seriale con il firmware Espruino, la scheda micro:bit della BBC funge da read-eval-print loop (REPL) JavaScript, in modo simile a una shell Node.js. Poi 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 nuova riga (\n) per indicare al 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 impostare il sistema in uno stato noto e impedirgli di richiamare i caratteri inviati, dobbiamo premere 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. Verifica di poter inviare correttamente un comando:

  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. Apri la scheda Console in Chrome DevTools e digita writeToStream('console.log("yes")');.

Sulla pagina dovrebbe essere stampato qualcosa di simile a questo:

a13187e7e6260f7f.png

5. Controllare la matrice LED

Crea la stringa della griglia della matrice

Per controllare la matrice LED sul micro:bit, dobbiamo chiamare show(). Questo metodo mostra le immagini sullo schermo LED 5x5 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. Quindi, 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('')})`);

Agganciare le caselle di controllo per aggiornare la matrice

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

script.js - DOMContentLoaded

initCheckboxes();

Reimposta anche la griglia quando il micro:bit viene collegato 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 appropriate.

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 del selettore della porta seriale, seleziona il dispositivo micro:bit della BBC e fai clic su Connetti.
  4. Dovresti vedere uno smile sulla matrice LED micro:bit.
  5. Traccia uno schema diverso per la matrice LED modificando le caselle di controllo.

6. Collega i pulsanti micro:bit

Aggiungere un evento di visualizzazione sui pulsanti micro:bit

Ci sono due pulsanti sul micro:bit, uno ai lati 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 del selettore della porta seriale, seleziona il dispositivo micro:bit della BBC e fai clic su Connetti.
  4. Dovresti vedere uno smile sulla matrice LED dei micro:bit.
  5. Premi i pulsanti sul micro:bit e verifica che 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, il flusso dovrebbe essere suddiviso in singole righe e ogni messaggio dovrebbe essere visualizzato come una riga separata.

Trasformare i 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. Immaginala 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 prenderà lo stream e lo suddividerà in base alle interruzioni di riga (\r\n). Il corso richiede due metodi, transform e flush. Il metodo transform viene chiamato ogni volta che lo stream riceve nuovi dati. Può accodare i dati o salvarli per un secondo momento. Il metodo flush viene chiamato quando il flusso viene chiuso e gestisce tutti i dati non 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, eseguiremo semplicemente il flush dei dati rimanenti nel container utilizzando enqueue.

script.js - LineBreakTransformer.flush()

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

Infine, dobbiamo reindirizzare il flusso 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 micro:bit, i dati stampati dovrebbero essere restituiti su un'unica riga.

  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 uno smile sulla matrice LED micro:bit.
  5. Premi i pulsanti sul micro:bit e verifica che venga visualizzato qualcosa di simile al seguente:

6c2193880c748412.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 in formato JSON valido, è sufficiente restituire i dati in entrata.

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. Questo ci consente di semplificare l'JSONTransformer, poiché sappiamo che il codice JSON verrà inviato solo 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 del selettore della porta seriale, seleziona il dispositivo micro:bit della BBC e fai clic su Connetti.
  4. Dovresti vedere uno smile sulla matrice LED 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 del pulsante micro:bit, aggiorna readLoop per controllare se i dati ricevuti sono object con una proprietà button. Quindi, chiama buttonPushed per gestire la pressione 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 del selettore della porta seriale, seleziona il dispositivo micro:bit della BBC e fai clic su Connetti.
  4. Dovresti vedere uno smile sulla matrice LED dei micro:bit.
  5. Premi i pulsanti sul micro:bit e verifica che la matrice di 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.

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

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 suo 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 uno smile sulla matrice LED 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.

Tieni d'occhio https://goo.gle/fugu-api-tracker per conoscere le ultime novità sull'API Web Serial e per tutte le altre nuove ed entusiasmanti funzionalità web a cui il team di Chrome sta lavorando.