À propos de cet atelier de programmation
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.
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
- Une carte BBC micro:bit v1 avec le micrologiciel Espruino 2v04
- Une version récente de Chrome (80 ou ultérieure)
- Connaissance des langages HTML, CSS et JavaScript, ainsi que des outils pour les développeurs Chrome
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.
- Ouvrez un nouvel onglet dans votre navigateur et accédez à l'adresse https://web-serial-codelab-start.glitch.me/.
- Cliquez sur le lien Remix Glitch (Remixer Glitch) pour créer votre propre version du projet de démarrage.
- 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
- Chargez la page.
- 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.
- Actualisez la page.
- Cliquez sur le bouton Connexion.
- Dans la boîte de dialogue du sélecteur de port série, sélectionnez l'appareil micro:bit BBC, puis cliquez sur Connecter.
- Une icône doit s'afficher dans l'onglet pour indiquer que vous êtes connecté à un appareil série :
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.
- Actualisez la page.
- Cliquez sur le bouton Connexion.
- 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).
- Le logo Espruino doit s'afficher:
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:
- Actualisez la page.
- Cliquez sur le bouton Connexion.
- 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).
- 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:
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.
- Actualisez la page.
- Cliquez sur le bouton Connexion.
- Dans la boîte de dialogue du sélecteur de port série, sélectionnez l'appareil micro:bit BBC, puis cliquez sur Connecter.
- Un sourire devrait s'afficher sur la matrice de LED du micro:bit.
- 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.
- Actualisez la page.
- Cliquez sur le bouton Connexion.
- Dans la boîte de dialogue du sélecteur de port série, sélectionnez l'appareil micro:bit BBC, puis cliquez sur Connecter.
- Vous devriez voir un sourire sur la matrice LED micro:bits.
- 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.
- Actualisez la page.
- Cliquez sur le bouton Connexion.
- Dans la boîte de dialogue du sélecteur de port série, sélectionnez l'appareil micro:bit BBC, puis cliquez sur Connecter.
- Un sourire devrait s'afficher sur la matrice de LED du micro:bit.
- Appuyez sur les boutons du micro:bit et vérifiez que l'écran affiche quelque chose comme ceci :
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.
- Actualisez la page.
- Cliquez sur le bouton Connexion.
- Dans la boîte de dialogue du sélecteur de port série, sélectionnez l'appareil micro:bit BBC, puis cliquez sur Connecter.
- Un sourire devrait s'afficher sur la matrice de LED du micro:bit.
- 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.
- Actualisez la page.
- Cliquez sur le bouton Connexion.
- Dans la boîte de dialogue du sélecteur de port série, sélectionnez l'appareil micro:bit BBC, puis cliquez sur Connecter.
- Un sourire devrait s'afficher sur la matrice de LED du micro:bit.
- 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.
- Actualisez la page.
- Cliquez sur le bouton Connexion.
- Dans la boîte de dialogue du sélecteur de port série, sélectionnez l'appareil micro:bit BBC, puis cliquez sur Connecter.
- Un sourire devrait s'afficher sur la matrice de LED du micro:bit.
- 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.