Одна из причин, по которой мне действительно нравится моя работа, заключается в том, что я работаю с технологиями, которые мне нравятся. Две из этих технологий - это JavaScript и Интернет вещей. Я знаю, что вы можете подумать, что я сумасшедший, когда я скажу эту следующую часть, но один из моих любимых моментов прохождения - попытка заставить эти две технологии работать вместе. Взять то, что обычно считается «глупым» устройством, и сделать его умным с помощью JavaScript и Интернета. По этой причине я был очень взволнован, когда услышал о WebUSB.

API WebUSB позволяет нам получать доступ к USB-устройствам из браузера. В Интернете есть ряд руководств, статей и бесед, в которых объясняется, какова цель этой новой технологии и как ее использовать. В следующем списке представлены некоторые ресурсы, которые я использовал:

Все это отличные ресурсы, и их гораздо больше. Однако почти все ресурсы, которые я просмотрел для WebUSB, говорят об одной очень важной вещи:

Вы должны понимать, как работает стандарт USB, чтобы иметь возможность использовать этот API.

Это кажется разумным утверждением, чтобы использовать что-то, вы должны это понять, верно? Есть также действительно хорошие ресурсы для понимания стандарта USB, например USB in a NutShell. Если вы такой же веб-разработчик, как я, и читать сотни страниц об аппаратной архитектуре - не ваше дело, продолжайте читать.

Это сообщение в блоге представляет собой короткую (для веб-разработчиков) версию стандарта USB, части, которые мне понадобились для взлома некоторых USB-устройств с помощью JavaScript. Давайте взглянем на код (адаптированный из этого поста):

let vendorId = 0x00;
let device = await navigator
                     .usb
                     .requestDevice({ filters: [{ vendorId }] });
await device.open();
await device.selectConfiguration(1);
await device.claimInterface(2);
await device.controlTransferOut({
  requestType: 'class',
  recipient: 'interface',
  request: 0x22,
  value: 0x01,
  index: 0x02
});
let result = await device.transferIn(5, 64);
console.log(`Received: ${result}`);

API WebUSB в значительной степени полагается на обещания, как вы можете видеть в приведенном выше коде. Предполагая, что вы знакомы с обещаниями, давайте перейдем к частям, связанным с API:

ID поставщика

let vendorId = 0x00;

Идентификатор поставщика - это шестнадцатеричное число, которое присваивается USB-IF и производителем устройства. Этот идентификатор, а также идентификатор продукта можно добавить в фильтры метода запроса устройства. Если фильтры не указаны, то все USB-устройства, подключенные к вашему компьютеру, будут возвращены.

Запросить устройства

let device = await navigator
                     .usb
                     .requestDevice({ filters: [{ vendorId }] });

Этот метод может быть вызван только жестом пользователя, например нажатием кнопки. Это функция безопасности, это означает, что вы, как пользователь, должны инициировать сканирование USB-устройств, подключенных к вашему компьютеру. Это сканирование создает список устройств и позволяет выбрать одно для подключения.

Например, если я запустил на своем компьютере следующий код:

let device = await navigator.usb.requestDevice({ filters: [] });

Получаю результат:

Соединять

await device.open();

Выбор одного из устройств на изображении выше и нажатие «Подключить» означает, что вы даете этому веб-сайту разрешение на подключение к этому устройству. Соединение запускается путем вызова метода open().

Выбрать конфигурацию

await device.selectConfiguration(1);

Теперь, когда мы установили соединение, нам нужно определить, с какими конфигурациями устройства мы можем взаимодействовать. Не так много устройств, которые имеют более одной конфигурации. Конфигурация состоит из значений количества необходимой мощности, если устройство получает питание самостоятельно или от шины, и количества интерфейсов, которые оно имеет. Здесь важно помнить, что одновременно может быть включена только одна конфигурация. Включенная конфигурация - это то, как, например, ваш мобильный телефон узнает, подключен ли он к ноутбуку или прямо к электросети.

Интерфейс заявки

await device.claimInterface(2);

Затем мы должны потребовать интерфейс. Интерфейс - это группа функций устройства, которые вместе образуют одну функцию, которую устройство может выполнять. Утверждая интерфейс, мы берем под контроль эту особенность устройства. Мы делаем это, взаимодействуя с конечными точками ввода и вывода выбранного интерфейса.

Передача управления

await device.controlTransferOut({
  requestType: 'class',
  recipient: 'interface',
  request: 0x22,
  value: 0x01,
  index: 0x02
});

Этот метод отправляет сообщение с вашего компьютера на устройство. Методы передачи управления используются для настройки устройства. Он в значительной степени настраивает устройство, класс или интерфейс, чтобы иметь возможность общаться с вашим компьютером. Для этого необходимо установить ряд опций:

  1. requestType: указывает, является ли отправляемый нами запрос протоколом конкретного производителя, частью стандарта USB или, как в нашем коде, определенным классом на устройстве USB.
  2. recipient: устанавливает, передаем ли мы управление устройству в целом, определенной конечной точке или интерфейсу.
  3. request: определяет, что мы просим устройство сделать. Запросы могут быть заданы стандартом USB, спецификацией класса устройства или могут зависеть от поставщика.
  4. value и index: заполняются на основе предыдущих полей. В нашем примере value устанавливается на основе того, что ожидает спецификация класса, а index устанавливается на номер интерфейса, потому что наш recipient - это интерфейс.

Эти параметры вместе отправляются как заголовок в конечную точку передачи управления по умолчанию. Каждое USB-устройство имеет конечную точку по умолчанию, обычно endpointNumber 0.

Перечислить

let result = await device.transferIn(5, 64);
console.log(`Received: ${result}`);

Наконец, мы говорим, что хотим дождаться, пока устройство отправит нам некоторые данные. Мы предоставляем конечную точку, которую мы будем прослушивать, это конечная точка, отличная от конечной точки по умолчанию. Мы также указываем, сколько байтов мы ожидаем получить от этой конечной точки.

Почему так много цифр?

Возможно, вы сейчас думаете о том, почему в этом коде так много кажущихся случайными чисел?

Что ж, они не случайны, все они откуда-то, обычно из спецификаций устройства или поставщика, вот как вы можете их найти:

  • Идентификатор поставщика и / или идентификатор продукта: их можно узнать по-разному в зависимости от вашей операционной системы. Например, в MacOS вам нужно перейти к значку 🍎, выбрать Об этом Mac, Системный отчет… и в разделе Оборудование нажать USB. Вы также можете проверить этот список и посмотреть, есть ли там ваше устройство.
  • Конфигурация и интерфейс: если у вас есть технические данные USB-устройства, они должны быть там указаны. Если вы этого не сделаете, вы можете начать с 0 и попробовать несколько разных чисел. API WebUSB выдает ошибку, говоря, что конфигурация / интерфейс не существует. Если вы получили эту ошибку, увеличьте число на 1 и повторите попытку, пока не найдете правильное число.
  • Конечные точки и данные для передачи вход или выход: если вы пытаетесь играть с USB-устройством, которое не программируемый и не имеет драйверов с открытым исходным кодом, на которые вы могли бы взглянуть, тогда эта часть немного сложнее. Вам нужно будет установить фактические драйверы устройств на свой компьютер, а затем использовать такой инструмент, как Wireshark, чтобы увидеть, какие пакеты отправляются между устройством и вашим компьютером.

Вывод

WebUSB API предоставляет нам действительно интересные новые возможности для доступа к оборудованию с помощью JavaScript. Хотя все еще есть некоторые проблемы с безопасностью и довольно много проблем с поддержкой (см. Здесь), это все еще захватывающая перспектива, и я с нетерпением жду возможности узнать о ней больше.