API WebUSB делает USB более безопасным и простым в использовании, перенося его в Интернет.
Если я скажу прямо и просто «USB», велика вероятность, что вы сразу же подумаете о клавиатурах, мышах, аудио, видео и устройствах хранения данных. Вы правы, но вы найдете и другие типы устройств с универсальной последовательной шиной (USB).
Эти нестандартизированные USB-устройства требуют от поставщиков оборудования написания драйверов и SDK для конкретной платформы, чтобы вы (разработчик) могли ими воспользоваться. К сожалению, этот код, специфичный для платформы, исторически препятствовал использованию этих устройств в Интернете. И это одна из причин, по которой был создан WebUSB API: предоставить возможность предоставлять услуги USB-устройств в Интернете. С помощью этого API производители оборудования смогут создавать кроссплатформенные SDK JavaScript для своих устройств.
Но самое главное, это сделает USB безопаснее и проще в использовании, выведя его в Интернет .
Давайте посмотрим, какое поведение можно ожидать от WebUSB API:
- Купите USB-устройство.
- Подключите его к компьютеру. Сразу же появится уведомление с указанием нужного веб-сайта, на который можно перейти для этого устройства.
- Нажмите на уведомление. Сайт существует и готов к использованию!
- Нажмите, чтобы подключиться, и в Chrome появится окно выбора USB-устройства, где вы сможете выбрать свое устройство.
Тада!
Какой была бы эта процедура без WebUSB API?
- Установите приложение для конкретной платформы.
- Если он вообще поддерживается в моей операционной системе, убедитесь, что я загрузил нужную вещь.
- Установите вещь. Если вам повезет, вы не получите пугающих подсказок ОС или всплывающих окон, предупреждающих об установке драйверов/приложений из Интернета. Если вам не повезет, установленные драйверы или приложения будут работать неправильно и навредят вашему компьютеру. (Помните, что Интернет создан для того, чтобы содержать неработающие веб-сайты ).
- Если вы используете эту функци�� только один раз, код останется на вашем компьютере до тех пор, пока вы не решите его удалить. (В Интернете неиспользуемое место со временем освобождается.)
Прежде чем я начну
В этой статье предполагается, что у вас есть базовые знания о том, как работает USB. Если нет, то рекомендую прочитать USB в NutShell . Дополнительную информацию о USB см. в официальных спецификациях USB .
API WebUSB доступен в Chrome 61.
Доступно для исходных пробных версий
Чтобы получить как можно больше отзывов от разработчиков, использующих API WebUSB в полевых условиях, мы ранее добавили эту функцию в Chrome 54 и Chrome 57 в качестве исходной пробной версии .
Последнее судебное разбирательство успешно завершилось в сентябре 2017 года.
Конфиденциальность и безопасность
только HTTPS
Из-за возможностей этой функции она работает только в безопасных контекстах . Это означает, что вам нужно будет строить с учетом TLS .
Требуется жест пользователя
В целях безопасности navigator.usb.requestDevice()
можно вызывать только с помощью жеста пользователя, например касания или щелчка мыши.
Политика разрешений
Политика разрешений — это механизм, который позволяет разработчикам выборочно включать и отключать различные функции браузера и API. Его можно определить с помощью HTTP-заголовка и/или атрибута «allow» iframe.
Вы можете определить политику разрешений, которая контролирует, отображается ли атрибут usb
в объекте Navigator или, другими словами, разрешаете ли вы WebUSB.
Ниже приведен пример политики заголовка, в которой WebUSB не разрешен:
Feature-Policy: fullscreen "*"; usb "none"; payment "self" https://payment.example.com
Ниже приведен еще один пример политики контейнера, в которой разрешен USB:
<iframe allowpaymentrequest allow="usb; fullscreen"></iframe>
Давайте начнем кодировать
API WebUSB во многом опирается на JavaScript Promises . Если вы с ними не знакомы, ознакомьтесь с этим замечательным руководством по Promises . Еще одна вещь: () => {}
— это просто стрелочные функции ECMAScript 2015.
Получите доступ к USB-устройствам
Вы можете либо предложить пользователю выбрать одно подключенное USB-устройство, используя navigator.usb.requestDevice()
, либо вызвать navigator.usb.getDevices()
, чтобы получить список всех подключенных USB-устройств, к которым веб-сайту предоставлен доступ.
Функция navigator.usb.requestDevice()
принимает обязательный объект JavaScript, определяющий filters
. Эти фильтры используются для сопоставления любого USB-устройства с заданным идентификатором поставщика ( vendorId
) и, при необходимости, продукта ( productId
). Здесь также могут быть определены ключи classCode
, protocolCode
, serialNumber
и subclassCode
.
![Снимок экрана: приглашение пользователя USB-устройства в Chrome](https://cdn.statically.io/img/developer.chrome.google.cn/static/docs/capabilities/usb/image/screenshot-the-usb-devic-25e524dd3ed2b.png?hl=ru)
Например, вот как получить доступ к подключенному устройству Arduino, настроенному для разрешения источника.
navigator.usb.requestDevice({ filters: [{ vendorId: 0x2341 }] })
.then(device => {
console.log(device.productName); // "Arduino Micro"
console.log(device.manufacturerName); // "Arduino LLC"
})
.catch(error => { console.error(error); });
Прежде чем вы спросите, я не волшебным образом придумал это шестнадцатеричное число 0x2341
. Я просто искал слово «Arduino» в этом списке идентификаторов USB .
USB- device
возвращенное в рамках выполненного выше обещания, содержит некоторую базовую, но важную информацию об устройстве, такую как поддерживаемая версия USB, максимальный размер пакета, поставщик и идентификаторы продукта, количество возможных конфигураций, которые может иметь устройство. По сути, он содержит все поля USB-дескриптора устройства .
// Get all connected USB devices the website has been granted access to.
navigator.usb.getDevices().then(devices => {
devices.forEach(device => {
console.log(device.productName); // "Arduino Micro"
console.log(device.manufacturerName); // "Arduino LLC"
});
})
Кстати, если USB-устройство объявляет о поддержке WebUSB , а также определяет URL-адрес целевой страницы, Chrome будет отображать постоянное уведомление при подключении USB-устройства. При нажатии на это уведомление откроется целевая страница.
![Скриншот уведомления WebUSB в Chrome](https://cdn.statically.io/img/developer.chrome.google.cn/static/docs/capabilities/usb/image/screenshot-the-webusb-no-3e19033df3385.png?hl=ru)
Поговорите с USB-платой Arduino
Хорошо, теперь давайте посмотрим, насколько легко обмениваться данными с платы Arduino, совместимой с WebUSB, через порт USB. Ознакомьтесь с инструкциями по адресу https://github.com/webusb/arduino , чтобы включить WebUSB в ваших эскизах .
Не волнуйтесь, позже в этой статье я расскажу обо всех методах устройства WebUSB, упомянутых ниже.
let device;
navigator.usb.requestDevice({ filters: [{ vendorId: 0x2341 }] })
.then(selectedDevice => {
device = selectedDevice;
return device.open(); // Begin a session.
})
.then(() => device.selectConfiguration(1)) // Select configuration #1 for the device.
.then(() => device.claimInterface(2)) // Request exclusive control over interface #2.
.then(() => device.controlTransferOut({
requestType: 'class',
recipient: 'interface',
request: 0x22,
value: 0x01,
index: 0x02})) // Ready to receive data
.then(() => device.transferIn(5, 64)) // Waiting for 64 bytes of data from endpoint #5.
.then(result => {
const decoder = new TextDecoder();
console.log('Received: ' + decoder.decode(result.data));
})
.catch(error => { console.error(error); });
Имейте в виду, что библиотека WebUSB, которую я использую, реализует лишь один пример протокола (основанный на стандартном последовательном протоколе USB), и что производители могут создавать любой набор и типы конечных точек по своему желанию. Передача управления особенно удобна для небольших команд конфигурации, поскольку они получают приоритет шины и имеют четко определенную структуру.
А вот скетч, загруженный на плату Arduino.
// Third-party WebUSB Arduino library
#include <WebUSB.h>
WebUSB WebUSBSerial(1 /* https:// */, "webusb.github.io/arduino/demos");
#define Serial WebUSBSerial
void setup() {
Serial.begin(9600);
while (!Serial) {
; // Wait for serial port to connect.
}
Serial.write("WebUSB FTW!");
Serial.flush();
}
void loop() {
// Nothing here for now.
}
Сторонняя библиотека WebUSB Arduino , использованная в приведенном выше примере кода, выполняет в основном две вещи:
- Устройство действует как устройство WebUSB, позволяя Chrome читать URL-адрес целевой страницы .
- Он предоставляет последовательный API WebUSB, который вы можете использовать для переопределения API по умолчанию.
Посмотрите еще раз на код JavaScript. Как только я получаю device
, выбранное пользователем, device.open()
выполняет все шаги, специфичные для платформы, чтобы начать сеанс с USB-устройством. Затем мне нужно выбрать доступную конфигурацию USB с помощью device.selectConfiguration()
. Помните, что конфигурация определяет способ питания устройства, его максимальное энергопотребление и количество интерфейсов. Говоря об интерфейсах, мне также нужно запросить эксклюзивный доступ с помощью device.claimInterface()
, поскольку данные могут быть переданы на интерфейс или связанные с ним конечные точки только тогда, когда интерфейс заявлен. Наконец, вызов device.controlTransferOut()
необходим для настройки устройства Arduino с соответствующими командами для связи через последовательный API WebUSB.
Отсюда device.transferIn()
выполняет массовую передачу на устройство, чтобы сообщить ему, что хост готов к приему массовых данных. Затем обещание выполняется с помощью объекта result
, содержащего data
DataView , которые необходимо соответствующим образом проанализировать.
Если вы знакомы с USB, все это должно показаться вам довольно знакомым.
я хочу больше
API WebUSB позволяет в��аимодействовать со всеми типами USB-передачи/конечных точек:
- Передача CONTROL, используемая для отправки или получения параметров конфигурации или команд на USB-устройство, обрабатывается с помощью
controlTransferIn(setup, length)
иcontrolTransferOut(setup, data)
. - Передачи INTERRUPT, используемые для небольшого объема данных, чувствительных ко времени, обрабатываются теми же методами, что и BULK-передачи, с помощью
transferIn(endpointNumber, length)
иtransferOut(endpointNumber, data)
. - ИЗОХРОННЫЕ передачи, используемые для потоков данных, таких как видео и звук, обрабатываются с помощью
isochronousTransferIn(endpointNumber, packetLengths)
иisochronousTransferOut(endpointNumber, data, packetLengths)
. - BULK-передачи, используемые для надежной передачи большого количества независящих от времени данных, обрабатываются с помощью
transferIn(endpointNumber, length)
иtransferOut(endpointNumber, data)
.
Вы также можете взглянуть на проект Майка Цао WebLight , который представляет собой базовый пример создания светодиодного устройства с USB-управлением, разработанного для API WebUSB (здесь без использования Arduino). Вы найдете аппаратное обеспечение, программное обеспечение и прошивку.
Отменить доступ к USB-устройству
Веб-сайт может очистить разрешения на доступ к USB-устройству, которое ему больше не нужно, вызвав метод forget()
в экземпляре USBDevice
. Например, для образовательного веб-приложения, используемого на общем компьютере со многими устройствами, большое количество накопленных разрешений, созданных пользователями, ухудшает взаимодействие с пользователем.
// Voluntarily revoke access to this USB device.
await device.forget();
Поскольку forget()
доступна в Chrome 101 и более поздних версиях, проверьте, поддерживается ли эта функция, с помощью следующего:
if ("usb" in navigator && "forget" in USBDevice.prototype) {
// forget() is supported.
}
Ограничения на размер перевода
Некоторые операционные системы накладывают ограничения на объем данных, которые могут быть частью ожидающих транзакций USB. Разделение ваших данных на более мелкие транзакции и отправк�� только нескольких за раз помогает избежать этих ограничений. Это также уменьшает объем используемой памяти и позволяет вашему приложению сообщать о ходе выполнения передачи.
Поскольку несколько передач, отправленных в конечную точку, всегда выполняются по порядку, можно повысить пропускную способность, отправив несколько фрагментов в очередь, чтобы избежать задержки между передачами USB. Каждый раз, когда фрагмент полностью передается, он уведомляет ваш код о том, что он должен предоставить больше данных, как описано в примере вспомогательной функции ниже.
const BULK_TRANSFER_SIZE = 16 * 1024; // 16KB
const MAX_NUMBER_TRANSFERS = 3;
async function sendRawPayload(device, endpointNumber, data) {
let i = 0;
let pendingTransfers = [];
let remainingBytes = data.byteLength;
while (remainingBytes > 0) {
const chunk = data.subarray(
i * BULK_TRANSFER_SIZE,
(i + 1) * BULK_TRANSFER_SIZE
);
// If we've reached max number of transfers, let's wait.
if (pendingTransfers.length == MAX_NUMBER_TRANSFERS) {
await pendingTransfers.shift();
}
// Submit transfers that will be executed in order.
pendingTransfers.push(device.transferOut(endpointNumber, chunk));
remainingBytes -= chunk.byteLength;
i++;
}
// And wait for last remaining transfers to complete.
await Promise.all(pendingTransfers);
}
Советы
Отладка USB в Chrome упрощается благодаря внутренней странице about://device-log
, где вы можете увидеть все события, связанные с USB-устройствами, в одном месте.
![Снимок экрана страницы журнала устройства для отладки WebUSB в Chrome](https://cdn.statically.io/img/developer.chrome.google.cn/static/docs/capabilities/usb/image/screenshot-the-device-lo-5e45db14a43c3.png?hl=ru)
Внутренняя страница about://usb-internals
также пригодится и позволяет имитировать подключение и отключение виртуальных устройств WebUSB. Это может быть полезно для тестирования пользовательского интерфейса без использования реального оборудования.
![Скриншот внутренней страницы для отладки WebUSB в Chrome](https://cdn.statically.io/img/developer.chrome.google.cn/static/docs/capabilities/usb/image/screenshot-the-internal-3f26665cde1c.png?hl=ru)
В большинстве систем Linux USB-устройства по умолчанию имеют разрешения только на чтение. Чтобы разрешить Chrome открывать USB-устройство, вам необходимо добавить новое правило udev . Создайте файл /etc/udev/rules.d/50-yourdevicename.rules
со следующим содержимым:
SUBSYSTEM=="usb", ATTR{idVendor}=="[yourdevicevendor]", MODE="0664", GROUP="plugdev"
где [yourdevicevendor]
— 2341
, если ваше устройство, например, Arduino. ATTR{idProduct}
также можно добавить для более конкретного правила. Убедитесь, что ваш user
является членом группы plugdev
. Затем просто повторно подключите устройство.
Ресурсы
- Переполнение стека: https://stackoverflow.com/questions/tagged/webusb
- Спецификация API WebUSB: http://wicg.github.io/webusb/
- Статус функции Chrome: https://www.chromestatus.com/feature/5651917954875392 .
- Проблемы со спецификациями: https://github.com/WICG/webusb/issues .
- Ошибки реализации: http://crbug.com?q=comComponent:Blink>USB .
- WebUSB ❤ ️Arduino: https://github.com/webusb/arduino
- IRC: #webusb на IRC W3C
- Список рассылки WICG: https://lists.w3.org/Archives/Public/public-wicg/
- Проект WebLight: https://github.com/sowbug/weblight .
Отправьте твит @ChromiumDev , используя хэштег #WebUSB
, и сообщите нам, где и как вы его используете.
Благодарности
Спасибо Джо Медли за рецензирование этой стать��.