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 et afficher des images sur sa matrice 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.

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 en écriture

Prérequis

Nous avons choisi d'utiliser le micro:bit pour cet atelier de programmation, car il est abordable, offre quelques entrées (boutons) et sorties (affichage LED 5x5), et peut fournir des entrées et sorties supplémentaires. Consultez la page micro:bit de la BBC sur le site d'Espruino pour en savoir plus sur les capacités du 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é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ériels, tels que des microcontrôleurs et des imprimantes 3D.

Il existe de nombreux exemples de logiciels de contrôle en cours de développement à 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 sous la forme d'une application native empaquetée par le biais d'un framework tel que Electron. Dans d'autres cas, l'utilisateur doit effectuer une étape supplémentaire, telle que la copie d'une application compilée sur l'appareil à 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

Nous avons regroupé tout ce dont vous avez besoin pour cet atelier de programmation dans un projet Glitch.

  1. Ouvrez un nouvel onglet dans votre navigateur et accédez à 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 Show (Afficher), puis sélectionnez In a New Window (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

Commencez par 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 Web Serial n'est pas pris 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 permet d'éviter que l'interface utilisateur se bloque en cas d'attente d'entrée, mais cela est également important, car la page Web peut recevoir des données en série à tout moment et nous avons besoin d'un moyen de 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 voulons 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 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 a maintenant le strict minimum 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. Sur l'onglet, vous devriez voir une icône indiquant que vous avez 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. Nous allons d'abord obtenir le flux lisible à partir du port en appelant port.readable. Comme nous savons que nous allons récupérer le texte de l'appareil, nous le dirigeons vers 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 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 du sélecteur de port série, sélectionnez l'appareil micro:bit BBC, puis cliquez sur Connecter.
  4. Le logo Espruino doit s'afficher:

93494fd58ea835eb.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 avec le micrologiciel Espruino, la carte micro:bit de la BBC agit comme une boucle REPL (read-eval-print) JavaScript, comme 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 rédacteur à 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 à partir du 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 du sélecteur de port série, sélectionnez l'appareil micro:bit BBC, puis cliquez sur 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:

a13187e7e6260f7f.png

5. Contrôler la matrice 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 pour indiquer lesquels sont cochés ou non. 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 de caractéristiques (// CODELAB: Add feature detection here.), ajoutez la ligne suivante:

script.js - DOMContentLoaded

initCheckboxes();

Nous allons également réinitialiser la grille lorsque le micro:bit est connecté pour la première fois, de sorte qu'il affiche un visage souriant. La fonction drawGrid() est déjà fournie. Cette fonction fonctionne de la même manière que sendGrid(). il prend un tableau de 1 et de 0 et coche les cases appropriées.

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. Vous devriez voir un sourire sur la matrice LED micro:bit.
  5. Dessinez un schéma différent sur la matrice LED en cochant les cases.

6. Brancher les boutons micro:bit

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

Il y a deux boutons sur le micro:bit, 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 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);

Ensuite, nous devons brancher 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 visage souriant lorsque l'utilisateur est connecté, appuyer sur l'un des boutons du micro:bit permet d'ajouter à la page du texte indiquant sur quel bouton a été appuyé. Il est 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'il ajoute à la page le nouveau texte avec les détails du bouton enfoncé.

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

Gestion de base des flux

Lorsque l'un des boutons micro:bit est enfoncé, le micro:bit envoie les données au port série via un flux. Les flux sont très utiles, mais ils peuvent aussi poser problème, car vous n'obtiendrez pas nécessairement toutes les données en même temps, et elles peuvent être fragmentées arbitrairement.

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 individuelles et chaque message doit s'afficher 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 tout ce qui le consomme (dans ce cas, readLoop), et peut appliquer une transformation arbitraire avant d'être consommé. Considérez cela comme une chaîne d'assemblage: lorsqu'un widget s'achève sur la ligne, chaque étape de la ligne le modifie. Le temps qu'il arrive à sa destination finale, il est donc entièrement opérationnel.

Pour en savoir plus, consultez la page Concepts de l'API Streams 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é et traite 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. S'il y en a, divisez-les en tableau, puis itérez 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));

Une fois le flux 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 diriger le flux entrant vers 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 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. Vous devriez voir un sourire sur la matrice LED micro:bit.
  5. Appuyez sur les boutons du micro:bit et vérifiez que l'écran se présente comme suit:

6c2193880c748412.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, dirigez le flux dans JSONTransformer, après avoir traversé 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

Désormais, lorsque vous appuyez sur l'un des boutons micro:bit, [object Object] doit 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. Vous devriez voir un sourire sur la matrice LED micro:bit.
  5. Appuyez sur les boutons du micro:bit et vérifiez que l'écran se présente comme suit:

Réagir aux pressions sur les boutons

Pour répondre aux appuis sur le bouton micro:bit, mettez à jour readLoop pour vérifier si les données reçues sont de type 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 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

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. Vous devriez voir un sourire sur la matrice LED micro:bits.
  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 à brancher la fonctionnalité de déconnexion pour fermer le port lorsque l'utilisateur a terminé.

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

Fermez 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 la fermeture de l'objet outputDone:

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. Vous devriez voir un sourire sur la matrice LED micro:bit
  5. Appuyez sur le bouton Déconnecter, puis vérifiez que la matrice LED s'éteint et que la console ne comporte aucune erreur.

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.