Premiers pas avec l'API Web Serial

1. Introduction

Dernière mise à jour : 21/07/2020

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 LED 5x5. Vous découvrirez l'API Web Serial et apprendrez à utiliser les flux lisibles, inscriptibles et de transformation pour communiquer avec les appareils série via le navigateur.

81167ab7c01d353d.png

Points abordés

  • Ouvrir et fermer un port Web Serial
  • 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 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 sorties supplémentaires. Consultez la page BBC micro:bit sur le site Espruino pour en savoir plus sur les fonctionnalités de micro:bit.

2. À propos de l'API Web Serial

L'API Web Serial permet aux sites Web de lire et d'écrire sur un appareil série à 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.

De nombreux logiciels de contrôle sont conçus à l'aide de technologies 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 packagée via un framework tel qu'Electron. Dans d'autres cas, l'utilisateur doit effectuer une étape supplémentaire, comme copier une application compilée sur l'appareil à l'aide d'une clé USB.

L'expérience utilisateur peut être améliorée en permettant 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 de navigateur et accédez à https://web-serial-codelab-start.glitch.me/.
  2. Cliquez sur le lien Remix 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 compatible

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

Cette fonction vérifie 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 Web Serial n'est pas compatible.

Ouvrez le port série.

Ensuite, nous devons ouvrir le port série. Comme la plupart des API modernes, l'API Web Serial est asynchrone. Cela empêche l'UI de se bloquer lors de l'attente d'une entrée, mais c'est également important, car des données série peuvent être reçues par la page Web à tout moment, et nous avons besoin d'un moyen de les écouter.

Étant donné qu'un ordinateur peut comporter plusieurs périphériques série, lorsque le navigateur tente de demander un port, il invite l'utilisateur à choisir le périphérique 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. La carte BBC micro:bit utilise une connexion à 9 600 bauds entre la puce USB vers série et le processeur principal.

Connectons également le bouton "Connect" (Se connecter) et faisons en sorte qu'il appelle 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 strict minimum pour commencer. Lorsque l'utilisateur clique sur le bouton Connect (Connecter), il est invité à sélectionner l'appareil série auquel se connecter, puis la connexion à micro:bit est établie.

  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 BBC micro:bit, puis cliquez sur Connect (Connecter).
  4. Dans l'onglet, une icône devrait indiquer que vous êtes connecté à un appareil série :

d9d0d3966960aeab.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 à partir du port en appelant port.readable. Comme nous savons que nous allons recevoir du texte de l'appareil, nous allons le transmettre via 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 le contenu sans bloquer le thread principal. Lorsque de nouvelles données arrivent, le lecteur renvoie deux propriétés : value et un booléen done. Si la valeur de done est "true", le port a été fermé ou aucune autre donnée n'est reçue.

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 désormais se connecter à l'appareil et ajouter 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 du sélecteur de port série, sélectionnez l'appareil BBC micro:bit, puis cliquez sur Connect (Connecter).
  4. Le logo Espruino doit s'afficher :

93494fd58ea835eb.png

Configurer un flux de sortie pour envoyer des données au 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 redirigez-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 via un port série au micrologiciel Espruino, la carte BBC micro:bit agit comme une boucle de lecture-évaluation-impression (REPL) 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 obtient un writer à 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 à la 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 que le système passe à un état connu et cesse de renvoyer les caractères que nous lui envoyons, nous devons envoyer un 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 la carte micro:bit. Vérifions que nous pouvons envoyer une commande correctement :

  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 BBC micro:bit, puis cliquez sur Connect (Connecter).
  4. Ouvrez l'onglet Console dans les outils pour les développeurs Chrome, puis saisissez writeToStream('console.log("yes")');.

Le résultat suivant devrait s'afficher sur la page :

a13187e7e6260f7f.png

5. Contrôler la matrice LED

Créer la chaîne de grille matricielle

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

Nous allons parcourir 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 de nos cases à cocher est l'inverse de l'ordre des LED dans la matrice. Nous convertissons ensuite 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('')})`);

Associer les cases à cocher 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éinitialisons également la grille lorsque la micro:bit est connectée pour la première fois, afin qu'elle affiche un visage heureux. La fonction drawGrid() est déjà fournie. Cette fonction fonctionne de la même manière que sendGrid(). Elle prend un tableau de 1 et de 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, elle envoie un smiley. Cocher les cases met à jour l'affichage sur 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 BBC micro:bit, puis cliquez sur Connect (Connecter).
  4. Un smiley devrait s'afficher sur la matrice LED de la micro:bit.
  5. Dessinez un autre motif sur la matrice LED en modifiant les cases à cocher.

6. Connecter les boutons micro:bit

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

Le micro:bit comporte deux boutons, un de chaque côté de la matrice LED. Espruino fournit une fonction setWatch qui envoie un événement/rappel lorsque l'utilisateur appuie sur le bouton. Comme nous voulons écouter les deux boutons, nous allons rendre notre fonction générique et lui faire 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);

Ensuite, nous devons 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 lorsqu'il est connecté, appuyer sur l'un des boutons du micro:bit ajoutera du texte à la page indiquant quel bouton a été appuyé. Chaque caractère se trouvera probablement sur sa propre 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 BBC micro:bit, puis cliquez sur Connect (Connecter).
  4. Un smiley devrait s'afficher sur la matrice LED de la micro:bit.
  5. Appuyez sur les boutons du micro:bit et vérifiez qu'un nouveau texte est ajouté à la page avec les détails du bouton sur lequel vous avez appuyé.

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

Gestion de base des flux

Lorsqu'un des boutons 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 aussi être difficiles à gérer, car vous n'obtiendrez pas forcément toutes les données en même temps, et elles peuvent être regroupées de manière arbitraire.

L'application affiche actuellement le flux entrant au fur et à mesure de son arrivée (dans readLoop). Dans la plupart des cas, chaque caractère se trouve sur sa propre ligne, mais ce n'est pas très utile. Idéalement, le flux doit être analysé en lignes individuelles, et chaque message doit être affiché 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 les données analysées. Un flux de transformation peut se situer entre la source du flux (ici, le micro:bit) et ce qui consomme le flux (ici, readLoop). Il peut appliquer une transformation arbitraire avant d'être finalement consommé. Imaginez une chaîne de montage : à mesure qu'un widget avance sur la chaîne, chaque étape le modifie. Ainsi, lorsqu'il arrive à sa destination finale, il est entièrement fonctionnel.

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

Transformer le flux avec LineBreakTransformer

Créons une classe LineBreakTransformer, qui prendra un flux en entrée et le segmentera en fonction des sauts de ligne (\r\n). La classe a besoin de 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 mettre les données en file d'attente ou les enregistrer pour plus tard. La méthode flush est appelée lorsque le flux est fermé et gère toutes les données qui n'ont pas encore été traitées.

Dans notre 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 un tableau, puis parcourez 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 les données restantes dans le conteneur à l'aide de enqueue.

script.js - LineBreakTransformer.flush()

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

Enfin, nous devons rediriger le flux entrant vers le nouveau LineBreakTransformer. Notre flux d'entrée d'origine n'a été transmis que par un TextDecoderStream. Nous devons donc ajouter un pipeThrough supplémentaire pour le transmettre par 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 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 BBC micro:bit, puis cliquez sur Connect (Connecter).
  4. Un smiley devrait s'afficher sur la matrice LED de la micro:bit.
  5. Appuyez sur les boutons du micro:bit et vérifiez que vous voyez quelque chose comme suit :

6c2193880c748412.png

Transformer le flux avec JSONTransformer

Nous pourrions essayer d'analyser la chaîne en JSON dans readLoop, mais à la place, créons un transformateur JSON très simple qui transformera les données en objet JSON. Si les données ne sont pas au format JSON valide, renvoyez simplement ce qui a été reçu.

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 JSONTransformer, après qu'il soit passé par 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

À présent, lorsque vous appuyez sur l'un des boutons 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 BBC micro:bit, puis cliquez sur Connect (Connecter).
  4. Un smiley devrait s'afficher sur la matrice LED de la micro:bit.
  5. Appuyez sur les boutons du micro:bit et vérifiez que vous voyez quelque chose comme suit :

Répondre aux appuis sur les boutons

Pour répondre aux appuis sur les boutons micro:bit, mettez à jour readLoop afin de vérifier si les données reçues sont un object avec une propriété button. Appelez ensuite buttonPushed pour gérer l'appui 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 micro:bit, l'affichage de la matrice 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

Maintenant, lorsque vous appuyez sur l'un des boutons micro:bit, la matrice LED doit afficher un smiley ou un 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 BBC micro:bit, puis cliquez sur Connect (Connecter).
  4. Un smiley devrait s'afficher sur la matrice LED de la 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 à cancel étant asynchrone, nous devons utiliser await pour attendre qu'il se termine :

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 à votre guise.

  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 BBC micro:bit, puis cliquez sur Connect (Connecter).
  4. Un smiley devrait s'afficher sur la matrice LED micro:bit.
  5. Appuyez sur le bouton Déconnecter et vérifiez que la matrice 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 https://goo.gle/fugu-api-tracker pour en savoir plus sur l'API Web Serial et toutes les autres nouvelles fonctionnalités Web intéressantes sur lesquelles travaille l'équipe Chrome.