Premiers pas avec l'API Web Serial

Premiers pas avec l'API Web Serial

À propos de cet atelier de programmation

subjectDernière mise à jour : sept. 20, 2022
account_circleRédigé par Pete LePage and François Beaufort

1. Introduction

Dernière mise à jour : 19/09/2022

Objectifs de l'atelier

Dans cet atelier de programmation, vous allez créer une page Web qui utilise l'API Web Serial pour interagir avec une carte BBC micro:bit afin d'afficher des images sur sa matrice de LED 5x5. Vous découvrirez l'API Web Serial et apprendrez à utiliser des flux lisibles, accessibles en écriture et de transformation pour communiquer avec les appareils série via le navigateur.

67543f4caaaca5de.png

Points abordés

  • Ouvrir et fermer un port série Web
  • Utiliser une boucle de lecture pour gérer les données d'un flux d'entrée
  • Envoyer des données via un flux d'écriture

Prérequis

Nous avons choisi d'utiliser la version 1 du micro:bit pour cet atelier de programmation, car il est abordable, offre quelques entrées (boutons) et sorties (écran LED 5x5) et peut fournir des entrées et des sorties supplémentaires. Pour en savoir plus sur les capacités du micro:bit, consultez la page micro:bit de la BBC sur le site Espruino.

2. À propos de l'API Web Serial

L'API Web Serial permet aux sites Web de lire et d'écrire sur un appareil sériel à l'aide de scripts. L'API fait le lien entre le Web et le monde physique en permettant aux sites Web de communiquer avec des appareils série, tels que des microcontrôleurs et des imprimantes 3D.

Il existe de nombreux exemples de logiciels de contrôle créés à l'aide de la technologie Web. Exemple :

Dans certains cas, ces sites Web communiquent avec l'appareil via une application d'agent native installée manuellement par l'utilisateur. Dans d'autres cas, l'application est fournie dans une application native empaquetée via un framework tel qu'Electron. Dans d'autres cas, l'utilisateur doit effectuer une étape supplémentaire, telle que la copie d'une application compilée sur le périphérique à l'aide d'une clé USB.

L'expérience utilisateur peut être améliorée en fournissant une communication directe entre le site et l'appareil qu'il contrôle.

3. Configuration

Obtenir le code

Pour cet atelier de programmation, nous avons regroupé tout ce dont vous avez besoin dans un projet Glitch.

  1. Ouvrez un nouvel onglet dans votre navigateur et accédez à l'adresse https://web-serial-codelab-start.glitch.me/.
  2. Cliquez sur le lien Remix Glitch (Remixer Glitch) pour créer votre propre version du projet de démarrage.
  3. Cliquez sur le bouton Afficher, puis sélectionnez Dans une nouvelle fenêtre pour voir votre code en action.

4. Ouvrir une connexion série

Vérifier si l'API Web Serial est prise en charge

La première chose à faire est de vérifier si l'API Web Serial est compatible avec le navigateur actuel. Pour ce faire, vérifiez si serial se trouve dans navigator.

Dans l'événement DOMContentLoaded, ajoutez le code suivant à votre projet:

script.js - DOMContentLoaded

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

Cela permet de vérifier si Web Serial est compatible. Si c'est le cas, ce code masque la bannière indiquant que Web Serial n'est pas pris en charge.

Essayer

  1. Chargez la page.
  2. Vérifiez que la page n'affiche pas de bannière rouge indiquant que la fonctionnalité Web Serial n'est pas prise en charge.

Ouvrir le port série

Ensuite, nous devons ouvrir le port série. Comme la plupart des autres API modernes, l'API Web Serial est asynchrone. Cela empêche l'UI de se bloquer lorsqu'elle attend une entrée, mais c'est également important, car la page Web peut recevoir des données série à tout moment, et nous devons pouvoir les écouter.

Étant donné qu'un ordinateur peut posséder plusieurs appareils série, lorsque le navigateur tente de demander un port, il invite l'utilisateur à choisir l'appareil auquel se connecter.

Ajoutez le code suivant à votre projet :

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

L'appel requestPort invite l'utilisateur à indiquer l'appareil auquel il souhaite se connecter. L'appel de port.open ouvre le port. Nous devons également indiquer la vitesse à laquelle nous souhaitons communiquer avec l'appareil série. Le micro:bit de la BBC utilise une connexion de 9 600 bauds entre la puce USB vers série et le processeur principal.

Connectons également le bouton de connexion et demandons-lui d'appeler connect() lorsque l'utilisateur clique dessus.

Ajoutez le code suivant à votre projet:

script.js - clickConnect()

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

Essayer

Notre projet dispose désormais du minimum nécessaire pour commencer. En cliquant sur le bouton Connect (Connecter), l'utilisateur est invité à sélectionner le périphérique série auquel se connecter, puis à se connecter au micro:bit.

  1. Actualisez la page.
  2. Cliquez sur le bouton Connexion.
  3. Dans la boîte de dialogue du sélecteur de port série, sélectionnez l'appareil micro:bit BBC, puis cliquez sur Connecter.
  4. Une icône doit s'afficher dans l'onglet pour indiquer que vous êtes connecté à un appareil série :

e695daf2277cd3a2.png

Configurer un flux d'entrée pour écouter les données du port série

Une fois la connexion établie, nous devons configurer un flux d'entrée et un lecteur pour lire les données de l'appareil. Tout d'abord, nous allons obtenir le flux lisible du port en appelant port.readable. Comme nous savons que nous allons recevoir du texte de l'appareil, nous allons le transmettre à un décodeur de texte. Ensuite, nous allons obtenir un lecteur et démarrer la boucle de lecture.

Ajoutez le code suivant à votre projet :

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

La boucle de lecture est une fonction asynchrone qui s'exécute en boucle et attend du contenu sans bloquer le thread principal. Lorsque de nouvelles données arrivent, le lecteur renvoie deux propriétés: value et une valeur booléenne done. Si done est défini sur "true", le port a été fermé ou ne reçoit plus de données.

Ajoutez le code suivant à votre projet :

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

Essayer

Notre projet peut maintenant se connecter à l'appareil et ajoutera toutes les données reçues de l'appareil à l'élément de journal.

  1. Actualisez la page.
  2. Cliquez sur le bouton Connexion.
  3. Dans la boîte de dialogue de sélection du port série, sélectionnez l'appareil BBC micro:bit, puis cliquez sur Connect (Se connecter).
  4. Le logo Espruino doit s'afficher:

dd52b5c37fc4b393.png

Configurer un flux de sortie pour envoyer des données vers le port série

La communication série est généralement bidirectionnelle. En plus de recevoir des données du port série, nous voulons également envoyer des données au port. Comme pour le flux d'entrée, nous n'enverrons que du texte sur le flux de sortie vers le micro:bit.

Commencez par créer un flux d'encodeur de texte et dirigez-le vers 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;

Lorsqu'elle est connectée en série au micrologiciel Espruino, la carte BBC micro:bit agit comme une boucle de lecture-évaluation-impression (REPL, Read-Evaluate-Print Loop) JavaScript, semblable à celle que vous obtenez dans un shell Node.js. Nous devons ensuite fournir une méthode pour envoyer des données au flux. Le code ci-dessous récupère un éditeur à partir du flux de sortie, puis utilise write pour envoyer chaque ligne. Chaque ligne envoyée inclut un caractère de nouvelle ligne (\n) pour indiquer au micro:bit d'évaluer la commande envoyée.

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

Pour faire passer le système dans un état connu et l'empêcher de renvoyer les caractères que nous lui envoyons, nous devons envoyer un raccourci CTRL-C et désactiver l'écho.

script.js - connect()

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

Essayer

Notre projet peut désormais envoyer et recevoir des données depuis le micro:bit. Vérifions que nous pouvons envoyer correctement une commande:

  1. Actualisez la page.
  2. Cliquez sur le bouton Connexion.
  3. Dans la boîte de dialogue de sélection du port série, sélectionnez l'appareil BBC micro:bit, puis cliquez sur Connect (Se connecter).
  4. Ouvrez l'onglet Console dans les outils pour les développeurs Chrome, puis saisissez writeToStream('console.log("yes")');.

Le message suivant doit s'afficher sur la page:

15e2df0064b5de28.png

5. Contrôler la matrice de LED

Créer la chaîne de la grille matricielle

Pour contrôler la matrice LED sur le micro:bit, nous devons appeler show(). Cette méthode permet d'afficher les graphismes sur l'écran LED 5 x 5 intégré. Elle prend un nombre binaire ou une chaîne.

Nous allons itérer sur les cases à cocher et générer un tableau de 1 et de 0 indiquant celles qui sont cochées et celles qui ne le sont pas. Nous devons ensuite inverser le tableau, car l'ordre des cases à cocher est l'inverse de l'ordre des LED dans la matrice. Ensuite, nous convertissons le tableau en chaîne et créons la commande à envoyer au 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('')})`);

Cocher les cases pour mettre à jour la matrice

Ensuite, nous devons écouter les modifications apportées aux cases à cocher et, si elles changent, envoyer ces informations au micro:bit. Dans le code de détection des caractéristiques (// CODELAB: Add feature detection here.), ajoutez la ligne suivante :

script.js - DOMContentLoaded

initCheckboxes();

Réinitialisez également la grille lorsque le micro:bit est connecté pour la première fois afin qu'il affiche un visage heureux. La fonction drawGrid() est déjà fournie. Cette fonction fonctionne de manière semblable à sendGrid(). Elle prend un tableau de valeurs 1 et 0, et coche les cases à cocher en conséquence.

script.js - clickConnect()

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

Essayer

Désormais, lorsque la page ouvre une connexion au micro:bit, un visage souriant est envoyé. Cochez les cases pour mettre à jour l'affichage de la matrice LED.

  1. Actualisez la page.
  2. Cliquez sur le bouton Connexion.
  3. Dans la boîte de dialogue du sélecteur de port série, sélectionnez l'appareil micro:bit BBC, puis cliquez sur Connecter.
  4. Un sourire devrait s'afficher sur la matrice de LED du micro:bit.
  5. Dessinez un autre motif sur la matrice de LED en modifiant les cases à cocher.

6. Brancher les boutons micro:bit

Ajouter un événement de visionnage sur les boutons micro:bit

Le micro:bit comporte deux boutons, un de chaque côté de la matrice de LED. Espruino fournit une fonction setWatch qui envoie un événement/rappel lorsque le bouton est enfoncé. Comme nous voulons écouter les deux boutons, nous allons rendre notre fonction générique et lui demander d'imprimer les détails de l'événement.

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

Nous devons ensuite connecter les deux boutons (nommés BTN1 et BTN2 sur la carte micro:bit) chaque fois que le port série est connecté à l'appareil.

script.js - clickConnect()

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

Essayer

En plus d'afficher un smiley heureux lorsqu'il est connecté, le micro:bit ajoute du texte à la page indiquant le bouton enfoncé lorsque vous appuyez sur l'un des boutons. Il est très probable que chaque caractère figure sur une ligne distincte.

  1. Actualisez la page.
  2. Cliquez sur le bouton Connexion.
  3. Dans la boîte de dialogue du sélecteur de port série, sélectionnez l'appareil micro:bit BBC, puis cliquez sur Connecter.
  4. Vous devriez voir un sourire sur la matrice LED micro:bits.
  5. Appuyez sur les boutons du micro:bit et vérifiez qu'un nouveau texte est ajouté à la page avec des informations sur le bouton enfoncé.

7. Utiliser un flux de transformation pour analyser les données entrantes

Gestion de base des flux

Lorsqu'un des boutons du micro:bit est enfoncé, le micro:bit envoie des données au port série via un flux. Les flux sont très utiles, mais ils peuvent également représenter un défi, car vous n'obtiendrez pas nécessairement toutes les données en même temps, et elles peuvent être divisées de manière arbitraire.

L'application imprime actuellement le flux entrant dès qu'il arrive (dans readLoop). Dans la plupart des cas, chaque caractère figure sur sa propre ligne, mais ce n'est pas très utile. Idéalement, le flux doit être analysé en lignes distinctes, et chaque message doit apparaître sur sa propre ligne.

Transformer des flux avec TransformStream

Pour ce faire, nous pouvons utiliser un flux de transformation ( TransformStream), qui permet d'analyser le flux entrant et de renvoyer des données analysées. Un flux de transformation peut se trouver entre la source du flux (dans ce cas, le micro:bit) et l'élément qui consomme le flux (dans ce cas readLoop), et peut appliquer une transformation arbitraire avant qu'il ne soit finalement consommé. Pensez-y comme à une chaîne de montage : à mesure qu'un widget descend la chaîne, chaque étape de la chaîne le modifie, de sorte qu'à l'arrivée à sa destination finale, il s'agit d'un widget entièrement fonctionnel.

Pour en savoir plus, consultez les concepts de l'API Streams de MDN.

Transformer le flux avec LineBreakTransformer

Nous allons créer une classe LineBreakTransformer, qui intégrera un flux et le divisera en fonction des sauts de ligne (\r\n). La classe nécessite deux méthodes, transform et flush. La méthode transform est appelée chaque fois que de nouvelles données sont reçues par le flux. Il peut soit mettre les données en file d'attente, soit les enregistrer pour plus tard. La méthode flush est appelée lorsque le flux est fermé. Elle gère toutes les données qui n'ont pas encore été traitées.

Dans la méthode transform, nous allons ajouter de nouvelles données à container, puis vérifier s'il y a des sauts de ligne dans container. Si c'est le cas, divisez-le en tableau, puis itérez sur les lignes en appelant controller.enqueue() pour envoyer les lignes analysées.

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

Lorsque le flux est fermé, nous vidons simplement toutes les données restantes du conteneur à l'aide de enqueue.

script.js - LineBreakTransformer.flush()

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

Enfin, nous devons acheminer le flux entrant via le nouveau LineBreakTransformer. Notre flux d'entrée d'origine n'était acheminé que via un TextDecoderStream. Nous devons donc ajouter un pipeThrough supplémentaire pour le canaliser via notre nouveau 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()));

Essayer

Désormais, lorsque vous appuyez sur l'un des boutons du micro:bit, les données imprimées doivent être renvoyées sur une seule ligne.

  1. Actualisez la page.
  2. Cliquez sur le bouton Connexion.
  3. Dans la boîte de dialogue du sélecteur de port série, sélectionnez l'appareil micro:bit BBC, puis cliquez sur Connecter.
  4. Un sourire devrait s'afficher sur la matrice de LED du micro:bit.
  5. Appuyez sur les boutons du micro:bit et vérifiez que l'écran affiche quelque chose comme ceci :

eead3553d29ee581.png

Transformer le flux avec JSONTransformer

Nous pourrions essayer d'analyser la chaîne au format JSON dans readLoop, mais nous allons plutôt créer un transformateur JSON très simple qui transformera les données en un objet JSON. Si les données ne sont pas valides au format JSON, renvoyez simplement le contenu fourni.

script.js - JSONTransformer.transform

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

Ensuite, transmettez le flux via le JSONTransformer, après qu'il a traversé le LineBreakTransformer. Cela nous permet de simplifier notre JSONTransformer, car nous savons que le JSON ne sera envoyé que sur une seule ligne.

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

Essayer

Lorsque vous appuyez sur l'un des boutons du micro:bit, [object Object] devrait s'afficher sur la page.

  1. Actualisez la page.
  2. Cliquez sur le bouton Connexion.
  3. Dans la boîte de dialogue du sélecteur de port série, sélectionnez l'appareil micro:bit BBC, puis cliquez sur Connecter.
  4. Un sourire devrait s'afficher sur la matrice de LED du micro:bit.
  5. Appuyez sur les boutons du micro:bit et vérifiez que l'écran affiche quelque chose comme ceci :

Réagir aux pressions sur les boutons

Pour répondre aux pressions sur les boutons du micro:bit, mettez à jour readLoop pour vérifier si les données qu'il a reçues sont un object avec une propriété button. Appelez ensuite buttonPushed pour gérer la pression sur le bouton.

script.js - readLoop()

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

Lorsque vous appuyez sur un bouton du micro:bit, l'affichage de la matrice de LED doit changer. Utilisez le code suivant pour définir 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
();
 
}
}

Essayer

Désormais, lorsque vous appuyez sur l'un des boutons micro:bit, la matrice LED doit changer de visage en souriant ou en visage triste.

  1. Actualisez la page.
  2. Cliquez sur le bouton Connexion.
  3. Dans la boîte de dialogue du sélecteur de port série, sélectionnez l'appareil micro:bit BBC, puis cliquez sur Connecter.
  4. Un sourire devrait s'afficher sur la matrice de LED du micro:bit.
  5. Appuyez sur les boutons du micro:bit et vérifiez que la matrice LED change.

8. Fermer le port série

La dernière étape consiste à connecter la fonctionnalité de déconnexion pour fermer le port lorsque l'utilisateur a terminé.

Fermer le port lorsque l'utilisateur clique sur le bouton "Connecter/Déconnecter"

Lorsque l'utilisateur clique sur le bouton Connecter/Déconnecter, nous devons fermer la connexion. Si le port est déjà ouvert, appelez disconnect() et mettez à jour l'UI pour indiquer que la page n'est plus connectée à l'appareil série.

script.js - clickConnect()

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

Fermer les flux et le port

Dans la fonction disconnect, nous devons fermer le flux d'entrée, le flux de sortie et le port. Pour fermer le flux d'entrée, appelez reader.cancel(). L'appel de cancel est asynchrone. Nous devons donc utiliser await pour attendre la fin de l'opération:

script.js - disconnect()

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

Pour fermer le flux de sortie, obtenez un writer, appelez close() et attendez que l'objet outputDone soit fermé :

script.js - disconnect()

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

Enfin, fermez le port série et attendez qu'il se ferme :

script.js - disconnect()

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

Essayer

Vous pouvez maintenant ouvrir et fermer le port série quand vous le souhaitez.

  1. Actualisez la page.
  2. Cliquez sur le bouton Connexion.
  3. Dans la boîte de dialogue du sélecteur de port série, sélectionnez l'appareil micro:bit BBC, puis cliquez sur Connecter.
  4. Un sourire devrait s'afficher sur la matrice de LED du micro:bit.
  5. Appuyez sur le bouton Dissocier et vérifiez que la matrice de LED s'éteint et qu'aucune erreur ne s'affiche dans la console.

9. Félicitations

Félicitations ! Vous venez de créer votre première application Web qui utilise l'API Web Serial.

Consultez la page https://goo.gle/fugu-api-tracker pour obtenir les dernières informations sur l'API Web Serial et toutes les autres nouvelles fonctionnalités Web intéressantes sur lesquelles l'équipe Chrome travaille.