Interfejs Web Serial API umożliwia stronom internetowym komunikację z urządzeniami szeregowymi.
Co to jest Web Serial API?
Port szeregowy to dwukierunkowy interfejs komunikacyjny, który umożliwia wysyłanie bajt danych po bajcie.
Interfejs Web Serial API umożliwia witrynom odczyt i zapis w i urządzenia szeregowego z JavaScriptem. Urządzenia szeregowe są połączone za pomocą port szeregowy w systemie użytkownika lub przez wymienne urządzenia USB i Bluetooth, które emulują port szeregowy.
Innymi słowy, interfejs Web Serial API łączy sieć i świat fizyczny, umożliwiające witrynom komunikowanie się z urządzeniami szeregowymi, takimi jak mikrokontrolery, i drukarki 3D.
Ten interfejs API jest też świetnym uzupełnieniem WebUSB, ponieważ systemy operacyjne wymagają aplikacje do komunikowania się z niektórymi portami szeregowymi za pomocą ich szeregowego interfejsu API, a nie niskopoziomowego interfejsu USB API.
Sugerowane zastosowania
W sektorach edukacyjnych, hobbystycznych i przemysłowych użytkownicy łączą się z urządzeniami peryferyjnymi między urządzeniami i komputerami. Te urządzenia są często sterowane przez: przez połączenie szeregowe wykorzystywane przez specjalne oprogramowanie. Niektóre niestandardowe do sterowania tymi urządzeniami korzysta z technologii internetowej:
Czasem strony komunikują się z urządzeniem przez agenta którą użytkownicy zainstalowali ręcznie. W innych aplikacja jest w postaci pakietu aplikacji za pomocą platformy takiej jak Electron. W innych przypadkach użytkownik musi wykonać dodatkową czynność, taką jak kopiowanie skompilowanej aplikacji na urządzenie za pomocą dysku flash USB.
We wszystkich tych przypadkach poprawa wrażeń użytkowników poprzez bezpośrednie między stroną internetową a urządzeniem, którym zarządza.
Obecny stan,
Krok | Stan |
---|---|
1. Utwórz wyjaśnienie | Zakończono |
2. Utwórz początkową wersję roboczą specyfikacji | Zakończono |
3. Zbieraj opinie iterować projekt | Zakończono |
4. Wersja próbna origin | Zakończono |
5. Wprowadzenie na rynek | Zakończono |
Używanie interfejsu Web Serial API
Wykrywanie cech
Aby sprawdzić, czy interfejs Web Serial API jest obsługiwany, użyj polecenia:
if ("serial" in navigator) {
// The Web Serial API is supported.
}
Otwórz port szeregowy
Interfejs Web Serial API jest z założenia asynchroniczny. Uniemożliwia to interfejsowi witryny w trakcie oczekiwania na dane wejściowe, co jest ważne, ponieważ dane seryjne mogą być w dowolnym momencie, i wymagać możliwości słuchania.
Aby otworzyć port szeregowy, najpierw uzyskaj dostęp do obiektu SerialPort
. Aby to zrobić,
albo poproś użytkownika o wybranie pojedynczego portu szeregowego, wywołując
navigator.serial.requestPort()
w odpowiedzi na gest użytkownika, taki jak dotyk
lub kliknięciem przycisku myszy. Możesz też wybrać jeden z nich z listy navigator.serial.getPorts()
, który zwróci
lista portów szeregowych, do których witryna ma dostęp;
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();
Funkcja navigator.serial.requestPort()
przyjmuje opcjonalny literał obiektu
która definiuje filtry. Służą one do dopasowywania dowolnych urządzeń szeregowych podłączonych
USB z wymaganym dostawcą USB (usbVendorId
) i opcjonalnym urządzeniem USB
(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();
Gdy zadzwonisz pod requestPort()
, użytkownik będzie mógł wybrać urządzenie i zwróci
SerialPort
obiekt. Gdy masz już obiekt SerialPort
, wywołanie port.open()
z wybraną szybkością transmisji spowoduje otwarcie portu szeregowego. Słownik baudRate
element określa szybkość przesyłania danych przez linię szeregową. Jest wyrażony w
w bitach na sekundę (b/s). Zapoznaj się z dokumentacją urządzenia, aby dowiedzieć się,
ma prawidłową wartość, bo wszystkie wysyłane i odbierane dane będą bezużyteczne.
nieprawidłowo określono. Niektóre urządzenia USB i Bluetooth, które emulują kod szeregowy
port może mieć dowolną wartość, ponieważ jest ignorowana przez
za pomocą emulacji.
// 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 });
Możesz też określić dowolną z poniższych opcji podczas otwierania portu szeregowego. Te opcje są opcjonalne i mają wygodne wartości domyślne.
dataBits
: liczba bitów danych na klatkę (7 lub 8).stopBits
: liczba bitów zatrzymania na końcu klatki (1 lub 2).parity
: tryb parzystości ("none"
,"even"
lub"odd"
).bufferSize
: rozmiar buforów odczytu i zapisu, które powinny zostać utworzone (musi być mniejszy niż 16 MB).flowControl
: tryb sterowania przepływem ("none"
lub"hardware"
).
Odczyt z portu szeregowego
Strumienie wejściowe i wyjściowe w interfejsie Web Serial API są obsługiwane przez Streams API.
Po ustanowieniu połączenia przez port szeregowy readable
i writable
właściwości z obiektu SerialPort
zwracają obiekty ReadableStream i
WritableStream. Te dane będą używane do odbierania danych z usług i wysyłania ich do
urządzenia szeregowego. Do przesyłania danych obie używają instancji Uint8Array
.
Gdy z urządzenia szeregowego przychodzą nowe dane, port.readable.getReader().read()
zwraca asynchronicznie dwie właściwości: value
i done
. Jeśli
done
ma wartość prawda, port szeregowy został zamknięty lub nie ma już przesyłanych danych
cal Wywołanie port.readable.getReader()
powoduje utworzenie czytnika i blokowanie użytkownika readable
. Gdy urządzenie readable
jest zablokowane, nie można zamknąć portu szeregowego.
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);
}
Niektóre niekrytyczne błędy odczytu portów szeregowych mogą wystąpić w pewnych sytuacjach, na przykład
przepełnienie bufora, błędy kadrowania i błędy parzystości. Rzucane jako
wyjątków i można je złapać, dodając kolejną pętlę na poprzednią
sprawdza port.readable
. To działa, jeśli błędy są
niekrytyczny, automatycznie tworzony jest nowy obiekt ReadableStream. W przypadku błędu krytycznego
na przykład urządzenie szeregowe zostanie usunięte, wówczas port.readable
stanie się
wartość 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.
}
}
Jeśli urządzenie szeregowe odeśle tekst, możesz użyć potoku port.readable
przez
TextDecoderStream
, jak widać poniżej. TextDecoderStream
to strumień przekształcenia
który przechwytuje wszystkie Uint8Array
fragmenty i konwertuje je w ciągi.
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);
}
Możesz kontrolować sposób przydzielania pamięci podczas odczytywania danych ze strumienia za pomocą opcji „przynieś własny bufor” . Wywołaj port.readable.getReader({ mode: "byob" })
, aby uzyskać interfejs ReadableStreamBYOBReader i podaj własny ArrayBuffer
przy wywoływaniu funkcji read()
. Pamiętaj, że interfejs Web Serial API obsługuje tę funkcję w Chrome 106 i nowszych wersjach.
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()...
}
}
Oto przykład ponownego użycia bufora z 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`.
}
Oto inny przykład odczytywania określonej ilości danych z portu szeregowego:
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);
Zapisz w porcie szeregowym
Aby wysłać dane na urządzenie szeregowe, przekaż je do
port.writable.getWriter().write()
Dzwonię do: releaseLock()
wł.
Aby port szeregowy można było później zamknąć, wymagany jest element port.writable.getWriter()
.
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();
Wyślij SMS-a na urządzenie za pomocą potoku TextEncoderStream
przesyłanego do: port.writable
jak pokazano poniżej.
const textEncoder = new TextEncoderStream();
const writableStreamClosed = textEncoder.readable.pipeTo(port.writable);
const writer = textEncoder.writable.getWriter();
await writer.write("hello");
Zamknij port szeregowy
port.close()
zamyka port szeregowy, jeśli jego elementy readable
i writable
są odblokowane, co oznacza, że wezwano użytkownika releaseLock()
do odpowiednich
czytelnika i pisarza.
await port.close();
Jednak podczas ciągłego odczytu danych z urządzenia szeregowego za pomocą pętli,
Urządzenie port.readable
będzie zawsze zablokowane, dopóki nie wystąpi błąd. W tym
wywołanie reader.cancel()
wymusi rozwiązanie reader.read()
natychmiast za pomocą { value: undefined, done: true }
, dzięki czemu funkcja
zapętlanie funkcji 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;
});
Zamykanie portu szeregowego jest bardziej skomplikowane w przypadku korzystania ze strumieni przekształcania. Zadzwoń pod numer reader.cancel()
tak jak poprzednio.
Następnie zadzwoń pod numer writer.close()
i port.close()
. Spowoduje to rozpowszechnianie błędów przez
strumienie przekształcenia do odpowiedniego portu szeregowego. Ponieważ propagacja błędów
nie dzieje się od razu, musisz użyć funkcji readableStreamClosed
oraz
Funkcja writableStreamClosed
obiecuje utworzone wcześniej, aby wykryć, kiedy port.readable
i port.writable
zostały odblokowane. Anulowanie reader
powoduje, że
strumień do przerwania; dlatego musisz wychwycić i zignorować błąd.
// 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();
Nasłuchuj połączenia i rozłączania
Jeśli port szeregowy jest zapewniony przez urządzenie USB, można je podłączyć do
lub odłączony od systemu. W przypadku gdy zezwolono witrynie na
dostępu do portu szeregowego, powinien monitorować zdarzenia connect
i 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.
});
Sygnały
Po nawiązaniu połączenia przez port szeregowy możesz bezpośrednio sygnałów ujawnianych przez port szeregowy do wykrywania urządzeń i kontrolowania przepływu. Te sygnały są zdefiniowane jako wartości logiczne. Na przykład niektóre urządzenia, takie jak Arduino, przejdzie w tryb programowania, jeśli sygnał Data Terminal Ready (DTR) będzie przełączono.
Aby skonfigurować sygnały wyjściowe i uzyskać sygnały wejściowe, należy odpowiednio:
Dzwonię do: port.setSignals()
i port.getSignals()
. Zobacz przykłady ich użycia poniżej.
// 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}`);
Przekształcanie strumieni
Gdy odbierasz dane z urządzenia szeregowego, niekoniecznie są to wszystkie wszystkie dane naraz. Może być dowolnie podzielony na fragmenty. Więcej informacji: Pojęcia związane z interfejsem Streams API.
Aby sobie z tym poradzić, możesz użyć wbudowanych strumieni przekształcenia, takich jak:
TextDecoderStream
lub utwórz własny strumień przekształcenia, który umożliwia
przeanalizować strumień przychodzący i zwrócić przeanalizowane dane. Strumień przekształcenia leży
między urządzeniem szeregowym a pętlą odczytu, która pobiera strumień. Może
zastosowanie dowolnego przekształcenia przed przetworzeniem danych. Pomyśl o tym jak o tym,
wiersz montażowy: w miarę jak widżet przesuwa się po linii, każdy krok w wierszu zmienia
w widżecie, dzięki czemu, zanim dotrze do miejsca docelowego, będzie całkowicie
działającego widżetu.
Zastanów się na przykład, jak utworzyć klasę strumienia przekształcania, która pobiera
i dzielić go na fragmenty na podstawie podziałów wierszy. Jego metoda transform()
nazywa się
za każdym razem, gdy strumień odbierze nowe dane. Może dodać dane do kolejki lub
zapisz je na później. Metoda flush()
jest wywoływana po zamknięciu strumienia.
obsługuje on wszystkie dane, które nie zostały jeszcze przetworzone.
Aby użyć klasy strumienia przekształcenia, musisz wprowadzić potok przychodzący
. W trzecim przykładzie w sekcji Odczyt z portu szeregowego
oryginalny strumień danych wejściowych był przesyłany tylko przez TextDecoderStream
, więc
trzeba zadzwonić do pipeThrough()
i użyć potoku przez nasz nowy 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();
Do debugowania problemów z komunikacją z urządzeniami szeregowymi użyj metody tee()
port.readable
, aby podzielić strumienie trafiające do lub z urządzenia szeregowego. Obie
strumienie mogą być wykorzystywane niezależnie,
a dzięki temu można wydrukować jedną
do konsoli w celu sprawdzenia.
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.
Unieważnij dostęp do portu szeregowego
Strona może wyczyścić uprawnienia dostępu do portu szeregowego, do którego już nie ma dostępu
chcesz zachować zainteresowanie, wywołując funkcję forget()
w wystąpieniu SerialPort
. Dla:
przykładowa edukacyjna aplikacja internetowa używana na współdzielonym komputerze
tych urządzeń, duża liczba zebranych uprawnień użytkowników powoduje,
użytkowników.
// Voluntarily revoke access to this serial port.
await port.forget();
Funkcja forget()
jest dostępna w Chrome 103 i nowszych wersjach, więc sprawdź, czy jest ona
obsługiwane przez:
if ("serial" in navigator && "forget" in SerialPort.prototype) {
// forget() is supported.
}
Wskazówki dla programistów
Debugowanie interfejsu Web Serial API w Chrome jest łatwe dzięki stronie wewnętrznej,
about://device-log
, gdzie możesz zobaczyć wszystkie zdarzenia związane z urządzeniami szeregowymi w jednym
w jednym miejscu.
Ćwiczenia z programowania
W ćwiczeniach z programowania w Google Developers będziesz używać interfejsu Web Serial API do interakcji i platformy BBC micro:bit, aby wyświetlać obrazy na matrycy LED 5 x 5.
Obsługa przeglądarek
Interfejs Web Serial API jest dostępny na wszystkich platformach komputerowych (ChromeOS, Linux, macOS, i Windows) w Chrome 89.
Watolina
Na Androidzie można korzystać z portów szeregowych opartych na USB przy użyciu interfejsu WebUSB API. oraz kod polyfill Serial API. Ten kod polyfill jest ograniczony do sprzętu, platformy, na których urządzenie jest dostępne za pośrednictwem interfejsu WebUSB API, ponieważ nie jest zostało zgłoszone przez wbudowany sterownik urządzenia.
Bezpieczeństwo i prywatność
Autorzy specyfikacji zaprojektowali i wdrożyli interfejs Web Serial API za pomocą podstawowego zasad zdefiniowanych w artykule Kontrolowanie dostępu do zaawansowanych funkcji platform internetowych, takich jak kontrola użytkownika, przejrzystość i ergonomia. Możliwość korzystania z tej karty Interfejs API jest chroniony głównie przez model uprawnień, który przyznaje dostęp tylko urządzenia szeregowego jednocześnie. W odpowiedzi na prośbę użytkownika musi on podjąć decyzję kroków, aby wybrać konkretne urządzenie szeregowe.
Aby poznać zalety związane z bezpieczeństwem, zapoznaj się z informacjami o bezpieczeństwie i prywatności Web Serial API Explainer.
Prześlij opinię
Zespół Chrome chętnie pozna Twoje przemyślenia i doświadczenia dotyczące Web Serial API.
Opowiedz nam o konstrukcji interfejsu API
Czy jest coś, co w interfejsie API nie działa zgodnie z oczekiwaniami? Czy są jeśli brakuje metod lub właściwości niezbędnych do realizacji pomysłu?
Zgłoś problem ze specyfikacją w repozytorium Web Serial API na GitHubie lub dodaj przemyślenia na temat istniejącego problemu.
Zgłoś problem z implementacją
Czy wystąpił błąd z implementacją Chrome? Czy wdrożenie różni się od specyfikacji?
Zgłoś błąd na https://new.crbug.com. Podaj jak najwięcej
jak najwięcej szczegółów, podać proste instrukcje dotyczące odtworzenia błędu oraz
Ustawiono Komponenty na Blink>Serial
. Glitch działa świetnie:
szybkie i łatwe udostępnianie.
Pokaż wsparcie
Czy zamierzasz korzystać z interfejsu Web Serial API? Wasze publiczne wsparcie pomaga nam ulepszyć Chrome nadaje priorytet funkcjom i pokazuje innym dostawcom przeglądarek, jak ważne jest, ich wsparcie.
Wyślij tweeta na adres @ChromiumDev, używając hashtagu
#SerialAPI
.
i daj nam znać, gdzie i jak go używasz.
Przydatne linki
- Specyfikacja
- Śledzenie błędu
- Wpis na temat ChromeStatus.com
- Komponent Blink:
Blink>Serial
Prezentacje
Podziękowania
Dziękujemy Reilly Grant i Joe Medley za opinię o tym artykule. Zdjęcie fabryki samolotu wykonane przez Birmingham Museums Trust na kanale Unsplash.