L'API Web Serial permet aux sites Web de communiquer avec des appareils série.
Qu'est-ce que l'API Web Serial ?
Un port série est une interface de communication bidirectionnelle qui permet d’envoyer et recevoir les données octet par octet.
L'API Web Serial permet aux sites Web de lire et d'écrire dans un en série avec JavaScript. Les appareils série sont connectés via un port série sur le système de l'utilisateur ou via des périphériques USB et Bluetooth amovibles qui émulent un port série.
En d'autres termes, l'API Web Serial fait le lien entre le Web et le monde physique en permettant aux sites Web de communiquer avec des appareils en série, tels que des microcontrôleurs et des imprimantes 3D.
Cette API accompagne parfaitement WebUSB, car les systèmes d'exploitation nécessitent pour communiquer avec certains ports série via leurs applications série plutôt que l'API USB de bas niveau.
Cas d'utilisation suggérés
Dans les secteurs éducatif, amateur et industriel, les utilisateurs connectent des périphériques appareils à leurs ordinateurs. Ces appareils sont souvent contrôlés par des microcontrôleurs via une connexion série utilisée par un logiciel personnalisé. Quelques valeurs personnalisées pour contrôler ces appareils est basé sur la technologie Web:
Dans certains cas, les sites Web communiquent avec l'appareil via un agent. que les utilisateurs ont installée manuellement. Dans d'autres, l'application est livré dans une application empaquetée via un framework comme Electron. Dans d'autres, l'utilisateur doit effectuer une étape supplémentaire, comme en copiant une application compilée sur l'appareil via une clé USB.
Dans tous ces cas, l'expérience utilisateur sera améliorée en fournissant entre le site Web et l'appareil qu'il contrôle.
État actuel
Étape | État |
---|---|
1. Créer une vidéo explicative | Fin |
2. Créer l'ébauche initiale de la spécification | Fin |
3. Recueillir des commentaires et itérer sur la conception | Fin |
4. Phase d'évaluation | Fin |
5. Lancement | Fin |
Utiliser l'API Web Serial
Détection de caractéristiques
Pour vérifier si l'API Web Serial est compatible, utilisez:
if ("serial" in navigator) {
// The Web Serial API is supported.
}
Ouvrir un port série
L'API Web Serial est de conception asynchrone. Cela empêche l'UI du site Web en attente d'une entrée, ce qui est important, car les données en série peuvent être reçues à tout moment, nécessitant un moyen de les écouter.
Pour ouvrir un port série, accédez d'abord à un objet SerialPort
. Pour cela, vous pouvez
soit inviter l'utilisateur à sélectionner un seul port série en appelant
navigator.serial.requestPort()
en réponse à un geste de l'utilisateur (par exemple, appuyer sur l'écran)
ou un clic de souris, ou choisissez-en une dans navigator.serial.getPorts()
, qui renvoie
une liste des ports série auxquels le site Web a accès.
document.querySelector('button').addEventListener('click', async () => {
// Prompt user to select any serial port.
const port = await navigator.serial.requestPort();
});
// Get all serial ports the user has previously granted the website access to.
const ports = await navigator.serial.getPorts();
La fonction navigator.serial.requestPort()
utilise un littéral d'objet facultatif
qui définit les filtres. Ceux-ci sont utilisés pour correspondre à tout
appareil série connecté via
USB avec fournisseur USB obligatoire (usbVendorId
) et produit USB en option
identifiants (usbProductId
).
// Filter on devices with the Arduino Uno USB Vendor/Product IDs.
const filters = [
{ usbVendorId: 0x2341, usbProductId: 0x0043 },
{ usbVendorId: 0x2341, usbProductId: 0x0001 }
];
// Prompt user to select an Arduino Uno device.
const port = await navigator.serial.requestPort({ filters });
const { usbProductId, usbVendorId } = port.getInfo();
L'appel de requestPort()
invite l'utilisateur à sélectionner un appareil et renvoie une
SerialPort
. Une fois que vous avez un objet SerialPort
, appeler port.open()
avec le débit en bauds souhaité
ouvre le port série. Le dictionnaire baudRate
Le membre spécifie la vitesse à laquelle
les données sont envoyées sur une ligne en série. Elle est exprimée de
en bits par seconde (bits/s). Consultez la documentation de votre appareil
la valeur correcte, car toutes les données que vous envoyez et recevez seront vides de sens s'il s'agit
spécifié de manière incorrecte. Pour certains appareils USB et Bluetooth qui émulent une liaison série
cette valeur peut être définie en toute sécurité sur n'importe quelle valeur, car elle est ignorée par le
émulation.
// Prompt user to select any serial port.
const port = await navigator.serial.requestPort();
// Wait for the serial port to open.
await port.open({ baudRate: 9600 });
Vous pouvez également spécifier l'une des options ci-dessous lors de l'ouverture d'un port série. Ces sont facultatives et sont associées à des valeurs par défaut pratiques.
dataBits
: nombre de bits de données par trame (7 ou 8).stopBits
: nombre de bits d'arrêt à la fin d'une trame (1 ou 2).parity
: mode de parité ("none"
,"even"
ou"odd"
).bufferSize
: taille des tampons de lecture et d'écriture à créer (la taille doit être inférieure à 16 Mo).flowControl
: mode de contrôle de flux ("none"
ou"hardware"
).
Lire à partir d'un port série
Les flux d'entrée et de sortie de l'API Web Serial sont gérés par l'API Streams.
Une fois la connexion au port série établie, readable
et writable
les propriétés de l'objet SerialPort
renvoient un élément ReadableStream et une
WritableStream. Ceux-ci seront utilisés pour recevoir et envoyer des données à
périphérique série. Les deux utilisent des instances Uint8Array
pour le transfert de données.
Lorsque de nouvelles données arrivent de l'appareil série, port.readable.getReader().read()
renvoie deux propriétés de manière asynchrone: value
et une valeur booléenne done
. Si
done
a la valeur "true", le port série a été fermé ou ne reçoit plus de données.
po. Appeler port.readable.getReader()
crée un lecteur et verrouille readable
sur
Lorsque readable
est verrouillé, le port série ne peut pas être fermé.
const reader = port.readable.getReader();
// Listen to data coming from the serial device.
while (true) {
const { value, done } = await reader.read();
if (done) {
// Allow the serial port to be closed later.
reader.releaseLock();
break;
}
// value is a Uint8Array.
console.log(value);
}
Certaines erreurs de lecture du port série non fatales peuvent se produire dans certaines conditions, par exemple :
de dépassement de mémoire tampon,
des erreurs de cadrage ou des erreurs de parité. Ils sont générés
et il peut être intercepté en ajoutant une autre boucle au-dessus de la précédente.
qui vérifie port.readable
. Cela fonctionne parce que tant
que les erreurs sont
non fatale, un flux ReadableStream est créé automatiquement. Si une erreur fatale
se produit, par exemple le retrait de l'appareil série, puis port.readable
devient
"null".
while (port.readable) {
const reader = port.readable.getReader();
try {
while (true) {
const { value, done } = await reader.read();
if (done) {
// Allow the serial port to be closed later.
reader.releaseLock();
break;
}
if (value) {
console.log(value);
}
}
} catch (error) {
// TODO: Handle non-fatal read error.
}
}
Si l'appareil série renvoie le message, vous pouvez rediriger port.readable
vers un
TextDecoderStream
comme indiqué ci-dessous. Un élément TextDecoderStream
est un flux de transformation.
qui récupère tous les fragments Uint8Array
et les convertit en chaînes.
const textDecoder = new TextDecoderStream();
const readableStreamClosed = port.readable.pipeTo(textDecoder.writable);
const reader = textDecoder.readable.getReader();
// Listen to data coming from the serial device.
while (true) {
const { value, done } = await reader.read();
if (done) {
// Allow the serial port to be closed later.
reader.releaseLock();
break;
}
// value is a string.
console.log(value);
}
Vous pouvez contrôler l'allocation de mémoire lorsque vous lisez des données dans un flux à l'aide de la fonctionnalité Bring Your Own Buffer. en lecture seule. Appelez port.readable.getReader({ mode: "byob" })
pour obtenir l'interface ReadableStreamBYOBReader et fournissez votre propre ArrayBuffer
lorsque vous appelez read()
. Notez que l'API Web Serial est compatible avec cette fonctionnalité dans Chrome 106 ou version ultérieure.
try {
const reader = port.readable.getReader({ mode: "byob" });
// Call reader.read() to read data into a buffer...
} catch (error) {
if (error instanceof TypeError) {
// BYOB readers are not supported.
// Fallback to port.readable.getReader()...
}
}
Voici un exemple de réutilisation du tampon de value.buffer
:
const bufferSize = 1024; // 1kB
let buffer = new ArrayBuffer(bufferSize);
// Set `bufferSize` on open() to at least the size of the buffer.
await port.open({ baudRate: 9600, bufferSize });
const reader = port.readable.getReader({ mode: "byob" });
while (true) {
const { value, done } = await reader.read(new Uint8Array(buffer));
if (done) {
break;
}
buffer = value.buffer;
// Handle `value`.
}
Voici un autre exemple de lecture d'une quantité spécifique de données à partir d'un port série:
async function readInto(reader, buffer) {
let offset = 0;
while (offset < buffer.byteLength) {
const { value, done } = await reader.read(
new Uint8Array(buffer, offset)
);
if (done) {
break;
}
buffer = value.buffer;
offset += value.byteLength;
}
return buffer;
}
const reader = port.readable.getReader({ mode: "byob" });
let buffer = new ArrayBuffer(512);
// Read the first 512 bytes.
buffer = await readInto(reader, buffer);
// Then read the next 512 bytes.
buffer = await readInto(reader, buffer);
Écrire sur un port série
Pour envoyer des données à un appareil série, transmettez les données à
port.writable.getWriter().write()
Appel de releaseLock()
sur
port.writable.getWriter()
est requis pour que le port série soit fermé ultérieurement.
const writer = port.writable.getWriter();
const data = new Uint8Array([104, 101, 108, 108, 111]); // hello
await writer.write(data);
// Allow the serial port to be closed later.
writer.releaseLock();
Envoyer un SMS à l'appareil via un TextEncoderStream
redirigé vers port.writable
comme indiqué ci-dessous.
const textEncoder = new TextEncoderStream();
const writableStreamClosed = textEncoder.readable.pipeTo(port.writable);
const writer = textEncoder.writable.getWriter();
await writer.write("hello");
Fermer un port série
port.close()
ferme le port série si ses membres readable
et writable
sont déverrouillées, ce qui signifie que releaseLock()
a été appelé pour obtenir
lecteur et écrivain.
await port.close();
Cependant, lors de la lecture continue des données
d'un appareil série à l'aide d'une boucle,
port.readable
sera toujours verrouillé jusqu'à ce qu'une erreur se produise. Dans ce
cas, l'appel de reader.cancel()
force reader.read()
à résoudre
immédiatement avec { value: undefined, done: true }
, ce qui permet
pour appeler reader.releaseLock()
.
// Without transform streams.
let keepReading = true;
let reader;
async function readUntilClosed() {
while (port.readable && keepReading) {
reader = port.readable.getReader();
try {
while (true) {
const { value, done } = await reader.read();
if (done) {
// reader.cancel() has been called.
break;
}
// value is a Uint8Array.
console.log(value);
}
} catch (error) {
// Handle error...
} finally {
// Allow the serial port to be closed later.
reader.releaseLock();
}
}
await port.close();
}
const closedPromise = readUntilClosed();
document.querySelector('button').addEventListener('click', async () => {
// User clicked a button to close the serial port.
keepReading = false;
// Force reader.read() to resolve immediately and subsequently
// call reader.releaseLock() in the loop example above.
reader.cancel();
await closedPromise;
});
La fermeture d'un port série est plus compliquée lorsque vous utilisez des flux de transformation. Appelez reader.cancel()
comme précédemment.
Appelez ensuite writer.close()
et port.close()
. Cela propage les erreurs à travers
le flux de transformation est dirigé vers le port série sous-jacent. Comme la propagation des erreurs
ne se produit pas immédiatement, vous devez utiliser readableStreamClosed
et
Promesses writableStreamClosed
créées précédemment pour détecter quand port.readable
et port.writable
ont été déverrouillés. L'annulation de reader
entraîne
d'un flux à abandonner ; c'est pourquoi vous devez intercepter et ignorer l'erreur qui en résulte.
// With transform streams.
const textDecoder = new TextDecoderStream();
const readableStreamClosed = port.readable.pipeTo(textDecoder.writable);
const reader = textDecoder.readable.getReader();
// Listen to data coming from the serial device.
while (true) {
const { value, done } = await reader.read();
if (done) {
reader.releaseLock();
break;
}
// value is a string.
console.log(value);
}
const textEncoder = new TextEncoderStream();
const writableStreamClosed = textEncoder.readable.pipeTo(port.writable);
reader.cancel();
await readableStreamClosed.catch(() => { /* Ignore the error */ });
writer.close();
await writableStreamClosed;
await port.close();
Écouter la connexion et la déconnexion
Si un port série est fourni par un périphérique USB, celui-ci peut être connecté
ou déconnecté du système. Lorsque le site Web a reçu l'autorisation de
accéder à un port série, il doit surveiller les événements connect
et disconnect
.
navigator.serial.addEventListener("connect", (event) => {
// TODO: Automatically open event.target or warn user a port is available.
});
navigator.serial.addEventListener("disconnect", (event) => {
// TODO: Remove |event.target| from the UI.
// If the serial port was opened, a stream error would be observed as well.
});
Gérer les signaux
Une fois la connexion au port série établie, vous pouvez explicitement interroger et définir les signaux exposés par le port série pour la détection des appareils et le contrôle de flux. Ces les signaux sont définis en tant que valeurs booléennes. Par exemple, certains appareils comme Arduino passe en mode de programmation si le signal DTR activé.
La configuration des signaux de sortie et l'obtention de signaux d'entrée sont respectivement effectuées
appelant port.setSignals()
et port.getSignals()
. Consultez les exemples d'utilisation ci-dessous.
// Turn off Serial Break signal.
await port.setSignals({ break: false });
// Turn on Data Terminal Ready (DTR) signal.
await port.setSignals({ dataTerminalReady: true });
// Turn off Request To Send (RTS) signal.
await port.setSignals({ requestToSend: false });
const signals = await port.getSignals();
console.log(`Clear To Send: ${signals.clearToSend}`);
console.log(`Data Carrier Detect: ${signals.dataCarrierDetect}`);
console.log(`Data Set Ready: ${signals.dataSetReady}`);
console.log(`Ring Indicator: ${signals.ringIndicator}`);
Transformer des flux
Lorsque vous recevez des données de l'appareil série, vous n'obtenez pas nécessairement toutes les les données en même temps. Il peut être fragmenté de manière arbitraire. Pour en savoir plus, consultez Concepts de l'API Streams
Pour résoudre ce problème, vous pouvez utiliser des flux de transformation intégrés tels que
TextDecoderStream
ou créer votre propre flux de transformation
qui analysent le flux entrant
et renvoient des données analysées. Le flux de transformation
entre l'appareil série et la boucle de lecture
qui utilise le flux. Il peut
d'appliquer une transformation arbitraire
avant que les données ne soient consommées. Pensez-y comme à
d'assemblage: au fur et à mesure qu'un widget arrive, chaque étape modifie
le widget, de sorte qu'au moment où il atteint sa destination finale, il est entièrement
et fonctionnel.
Voyons par exemple comment créer une classe de flux de transformation qui utilise un
et les divise en fonction des sauts de ligne. Sa 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
enregistrez-le pour
plus tard. La méthode flush()
est appelée lorsque le flux est fermé.
il traite toutes les données
qui n'ont pas encore été traitées.
Pour utiliser la classe de flux de transformation, vous devez diriger un flux entrant vers
Dans le troisième exemple de code sous Lire à partir d'un port série,
le flux d'entrée d'origine n'était acheminé que via un TextDecoderStream
. Nous avons donc
devez appeler pipeThrough()
pour le canaliser dans notre nouveau LineBreakTransformer
.
class LineBreakTransformer {
constructor() {
// A container for holding stream data until a new line.
this.chunks = "";
}
transform(chunk, controller) {
// Append new chunks to existing chunks.
this.chunks += chunk;
// For each line breaks in chunks, send the parsed lines out.
const lines = this.chunks.split("\r\n");
this.chunks = lines.pop();
lines.forEach((line) => controller.enqueue(line));
}
flush(controller) {
// When the stream is closed, flush any remaining chunks out.
controller.enqueue(this.chunks);
}
}
const textDecoder = new TextDecoderStream();
const readableStreamClosed = port.readable.pipeTo(textDecoder.writable);
const reader = textDecoder.readable
.pipeThrough(new TransformStream(new LineBreakTransformer()))
.getReader();
Pour déboguer les problèmes de communication d'un appareil série, utilisez la méthode tee()
de
port.readable
pour diviser les flux vers ou depuis l'appareil série. Les deux
les flux créés peuvent être utilisés indépendamment, ce qui vous permet d'imprimer
à la console pour inspection.
const [appReadable, devReadable] = port.readable.tee();
// You may want to update UI with incoming data from appReadable
// and log incoming data in JS console for inspection from devReadable.
Révoquer l'accès à un port série
Le site Web peut nettoyer les autorisations d'accès à un port série dont il n'est plus
que vous souhaitez conserver en appelant forget()
sur l'instance SerialPort
. Pour
exemple, pour une application Web éducative utilisée
sur un ordinateur partagé avec de nombreux
les appareils mobiles, un grand nombre d'autorisations accumulées par l'utilisateur
l'expérience utilisateur.
// Voluntarily revoke access to this serial port.
await port.forget();
Comme forget()
est disponible dans Chrome 103 ou version ultérieure, vérifiez si cette fonctionnalité est
compatible avec les éléments suivants:
if ("serial" in navigator && "forget" in SerialPort.prototype) {
// forget() is supported.
}
Conseils de développement
Déboguer l'API Web Serial dans Chrome est facile grâce à la page interne,
about://device-log
, où vous pouvez voir tous les événements liés aux appareils sériels au même endroit
un seul endroit.
Atelier de programmation
Dans l'atelier de programmation Google Developers, vous utiliserez l'API Web Serial pour interagir avec une carte BBC micro:bit pour afficher les images sur sa matrice LED 5x5.
Prise en charge des navigateurs
L'API Web Serial est disponible sur toutes les plates-formes de bureau (ChromeOS, Linux, macOS, et Windows) dans Chrome 89.
Polyfill
Sur Android, la prise en charge des ports série USB est possible grâce à l'API WebUSB. et le polyfill de l'API Serial. Ce polyfill est limité au matériel et plates-formes où l'appareil est accessible via l'API WebUSB, car il n'a pas ont été revendiquées par un pilote de périphérique intégré.
Sécurité et confidentialité
Les auteurs des spécifications ont conçu et mis en œuvre l'API Web Serial à l'aide de l'API définis dans l'article Contrôler l'accès à des fonctionnalités de plate-forme Web performantes, y compris le contrôle de l'utilisateur, la transparence et l'ergonomie. La possibilité d'utiliser L'API est principalement contrôlée par un modèle d'autorisation qui n'accorde l'accès qu'à un seul accès périphérique série à la fois. En réponse à l'invite de l'utilisateur, celui-ci doit prendre des mesures étapes pour sélectionner un appareil série particulier.
Pour comprendre les compromis en termes de sécurité, consultez les cours sur la sécurité et la confidentialité de la documentation explicative sur l'API Web Serial.
Commentaires
L'équipe Chrome aimerait connaître votre avis et votre expérience concernant la API Web Serial.
Présentez-nous la conception de l'API
Y a-t-il un aspect de l'API qui ne fonctionne pas comme prévu ? Ou y a-t-il des méthodes ou des propriétés dont vous avez besoin pour mettre en œuvre votre idée ?
Signalez un problème de spécification dans le dépôt GitHub de l'API Web Serial ou ajoutez votre à un problème existant.
Signaler un problème d'implémentation
Avez-vous détecté un bug dans l'implémentation de Chrome ? Ou l'implémentation différent des spécifications ?
Signalez un bug sur https://new.crbug.com. Assurez-vous d'inclure autant
détaillez le plus possible, fournissez des instructions simples pour reproduire le bug
Composants définis sur Blink>Serial
. Glitch fonctionne parfaitement
pour partager des répétitions
rapidement et facilement.
Montrez votre soutien
Prévoyez-vous d'utiliser l'API Web Serial ? Votre assistance publique permet au Chrome l'équipe à hiérarchiser les fonctionnalités et montre aux autres fournisseurs de navigateurs à quel point il est essentiel les accompagner.
Envoyez un tweet à @ChromiumDev en utilisant le hashtag.
#SerialAPI
et n'hésitez pas à nous dire où et comment vous l'utilisez.
Liens utiles
- Spécification
- Bug de suivi
- Entrée ChromeStatus.com
- Composant Blink:
Blink>Serial
Démonstrations
Remerciements
Merci à Reilly Grant et Joe Medley pour leurs commentaires sur cet article. Photo d'usine d'avions par le Birmingham Museums Trust sur Unsplash.