Начало работы с API веб-сериалов

1. Введение

Последнее обновление: 21 июля 2020 г.

Что ты построишь

В этой лабораторной работе вы создадите веб-страницу, которая использует API Web Serial для взаимодействия с платой BBC micro:bit для отображения изображений на ее светодиодной матрице 5x5. Вы узнаете о Web Serial API и о том, как использовать потоки, доступные для чтения, записи и преобразования, для связи с последовательными устройствами через браузер.

81167ab7c01d353d.png

Что вы узнаете

  • Как открыть и закрыть последовательный веб-порт
  • Как использовать цикл чтения для обработки данных из входного потока
  • Как отправить данные через поток записи

Что вам понадобится

Мы решили использовать micro:bit для этой лаборатории кода, потому что он доступен по цене, предлагает несколько входов (кнопок) и выходов (светодиодный дисплей 5x5), а также может обеспечить дополнительные входы и выходы. Подробную информацию о возможностях micro: bit можно найти на странице BBC micro :bit на сайте Espruino.

2. Об API веб-сериалов

Web Serial API предоставляет веб-сайтам возможность читать и записывать на последовательное устройство с помощью сценариев. API соединяет Интернет и физический мир, позволяя веб-сайтам взаимодействовать с последовательными устройствами, такими как микроконтроллеры и 3D-принтеры.

Существует множество примеров управляющего программного обеспечения, созданного с использованием веб-технологий. Например:

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

Пользовательский опыт можно улучшить, обеспечив прямую связь между сайтом и устройством, которым он управляет.

3. Приступаем к настройке

Получить код

Мы поместили все, что вам нужно для этой лаборатории кода, в проект Glitch.

  1. Откройте новую вкладку браузера и перейдите по адресу https://web-serial-codelab-start.glitch.me/ .
  2. Нажмите ссылку Remix Glitch , чтобы создать собственную версию стартового проекта.
  3. Нажмите кнопку «Показать» , а затем выберите «В новом окне» , чтобы увидеть свой код в действии.

4. Откройте последовательное соединение.

Проверьте, поддерживается ли API веб-серийного интерфейса.

Первое, что нужно сделать, — это проверить, поддерживается ли Web Serial API в текущем браузере. Для этого проверьте, есть ли serial в navigator .

В событии DOMContentLoaded добавьте в свой проект следующий код:

script.js - DOMContentLoaded

// CODELAB: Add feature detection here.
const notSupported = document.getElementById('notSupported');
notSupported.classList.toggle('hidden', 'serial' in navigator);

Это проверяет, поддерживается ли веб-сериал. Если это так, этот код скрывает баннер о том, что Web Serial не поддерживается.

Попробуйте это

  1. Загрузите страницу.
  2. Убедитесь, что на странице нет красного баннера о том, что веб-сериал не поддерживается.

Откройте последовательный порт

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

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

Добавьте в свой проект следующий код:

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 });

Вызов requestPort запрашивает у пользователя, к какому устройству он хочет подключиться. Вызов port.open открывает порт. Нам также необходимо обеспечить скорость, с которой мы хотим обмениваться данными с последовательным устройством. BBC micro:bit использует соединение со скоростью 9600 бод между чипом USB-последовательный порт и основным процессором.

Давайте также подключим кнопку подключения и сделаем так, чтобы она вызывала connect() , когда пользователь нажимает на нее.

Добавьте в свой проект следующий код:

script.js - clickConnect()

// CODELAB: Add connect code here.
await connect();

Попробуйте это

Теперь у нашего проекта есть минимум для начала работы. При нажатии кнопки «Подключиться» пользователю предлагается выбрать последовательное устройство для подключения, а затем подключиться к micro:bit.

  1. Перезагрузите страницу.
  2. Нажмите кнопку Подключиться .
  3. В диалоговом окне выбора последовательного порта выберите устройство BBC micro:bit и нажмите « Подключиться» .
  4. На вкладке вы должны увидеть значок, указывающий, что вы подключились к последовательному устройству:

d9d0d3966960aeab.png

Настройте входной поток для прослушивания данных из последовательного порта.

После того, как соединение установлено, нам необходимо настроить входной поток и считыватель для чтения данных с устройства. Сначала мы получим читаемый поток из порта, вызвав port.readable . Поскольку мы знаем, что будем получать текст обратно с устройства, мы пропустим его через текстовый декодер. Далее мы получим считыватель и запустим цикл чтения.

Добавьте в свой проект следующий код:

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();

Цикл чтения — это асинхронная функция, которая выполняется в цикле и ожидает содержимого, не блокируя основной поток. При поступлении новых данных средство чтения возвращает два свойства: value и логическое значение done . Если done истинно, порт закрыт или данные больше не поступают.

Добавьте в свой проект следующий код:

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;
  }
}

Попробуйте это

Теперь наш проект может подключиться к устройству и будет добавлять любые данные, полученные от устройства, в элемент журнала.

  1. Перезагрузите страницу.
  2. Нажмите кнопку Подключиться .
  3. В диалоговом окне выбора последовательного порта выберите устройство BBC micro:bit и нажмите « Подключиться» .
  4. Вы должны увидеть логотип Espruino:

93494fd58ea835eb.png

Настройте выходной поток для отправки данных в последовательный порт.

Последовательная связь обычно двунаправленная. Помимо получения данных из последовательного порта, мы также хотим отправить данные в порт. Как и в случае с входным потоком, мы будем отправлять текст только через выходной поток в micro:bit.

Сначала создайте поток кодировщика текста и направьте его в 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;

При последовательном подключении к прошивке Espruino плата BBC micro:bit действует как цикл чтения-оценки-печати JavaScript (REPL) , аналогично тому, что вы получаете в оболочке Node.js. Далее нам нужно предоставить метод для отправки данных в поток. Код ниже получает средство записи из выходного потока, а затем использует write для отправки каждой строки. Каждая отправляемая строка включает в себя символ новой строки ( \n ), сообщающий micro:bit о необходимости оценить отправленную команду.

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();

Чтобы перевести систему в известное состояние и запретить ей повторять символы, которые мы ей отправляем, нам нужно отправить CTRL-C и отключить эхо.

script.js - connect()

// CODELAB: Send CTRL-C and turn off echo on REPL
writeToStream('\x03', 'echo(false);');

Попробуйте это

Наш проект теперь может отправлять и получать данные с micro:bit. Давайте проверим, что мы можем правильно отправить команду:

  1. Перезагрузите страницу.
  2. Нажмите кнопку Подключиться .
  3. В диалоговом окне выбора последовательного порта выберите устройство BBC micro:bit и нажмите « Подключиться» .
  4. Откройте вкладку «Консоль» в Chrome DevTools и введите writeToStream('console.log("yes")');

На странице вы должны увидеть что-то вроде этого:

a13187e7e6260f7f.png

5. Управление светодиодной матрицей

Постройте строку матричной сетки

Чтобы управлять светодиодной матрицей на micro:bit, нам нужно вызвать show() . Этот метод показывает графику на встроенном светодиодном экране 5х5. Для этого требуется двоичное число или строка.

Мы пройдемся по флажкам и сгенерируем массив из единиц и нулей, указывающий, какие из них отмечены, а какие нет. Затем нам нужно перевернуть массив, поскольку порядок наших флажков противоположен порядку светодиодов в матрице. Далее мы преобразуем массив в строку и создаем команду для отправки в 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('')})`);

Подключите галочки для обновления матрицы

Далее нам нужно прослушать изменения в флажках и, если они изменятся, отправить эту информацию в micro:bit. В код обнаружения функций ( // CODELAB: Add feature detection here. ) добавьте следующую строку:

script.js - DOMContentLoaded

initCheckboxes();

Давайте также сбросим сетку при первом подключении micro:bit, чтобы она показывала счастливое лицо. Функция drawGrid() уже предоставлена. Эта функция работает аналогично sendGrid() ; он принимает массив из единиц и нулей и проверяет соответствующие флажки.

script.js - clickConnect()

// CODELAB: Reset the grid on connect here.
drawGrid(GRID_HAPPY);
sendGrid();

Попробуйте это

Теперь, когда страница открывает соединение с micro:bit, она отправляет счастливое лицо. Установка флажков обновит отображение на светодиодной матрице.

  1. Перезагрузите страницу.
  2. Нажмите кнопку Подключиться .
  3. В диалоговом окне выбора последовательного порта выберите устройство BBC micro:bit и нажмите « Подключиться» .
  4. Вы должны увидеть улыбку на светодиодной матрице micro:bit.
  5. Нарисуйте другой рисунок на светодиодной матрице, меняя флажки.

6. Подключите кнопки micro:bit.

Добавьте событие просмотра на кнопки micro:bit.

На micro:bit есть две кнопки, по одной с каждой стороны светодиодной матрицы. Espruino предоставляет функцию setWatch , которая отправляет событие/обратный вызов при нажатии кнопки. Поскольку мы хотим прослушивать обе кнопки, мы сделаем нашу функцию универсальной и заставим ее печатать подробную информацию о событии.

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);

Далее нам нужно подключать обе кнопки (названные BTN1 и BTN2 на плате micro:bit) каждый раз, когда последовательный порт подключается к устройству.

script.js - clickConnect()

// CODELAB: Initialize micro:bit buttons.
watchButton('BTN1');
watchButton('BTN2');

Попробуйте это

Помимо отображения счастливого лица при подключении, нажатие любой из кнопок micro:bit добавит на страницу текст, указывающий, какая кнопка была нажата. Скорее всего, каждый персонаж будет на своей линии.

  1. Перезагрузите страницу.
  2. Нажмите кнопку Подключиться .
  3. В диалоговом окне выбора последовательного порта выберите устройство BBC micro:bit и нажмите « Подключиться» .
  4. Вы должны увидеть улыбку на светодиодной матрице micro:bits.
  5. Нажмите кнопки на micro:bit и убедитесь, что он добавляет на страницу новый текст с подробностями о нажатой кнопке.

7. Используйте поток преобразования для анализа входящих данных.

Базовая обработка потока

При нажатии одной из кнопок micro:bit микро:бит отправляет данные в последовательный порт через поток. Потоки очень полезны, но они также могут стать проблемой, поскольку вы не обязательно получите все данные одновременно, и они могут быть произвольно разбиты на части.

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

Преобразование потоков с помощью TransformStream

Для этого мы можем использовать поток преобразования ( TransformStream ), который позволяет анализировать входящий поток и возвращать проанализированные данные. Поток преобразования может находиться между источником потока (в данном случае micro:bit) и всем, что потребляет поток (в данном случае readLoop ), и может применять произвольное преобразование до того, как оно окончательно будет использовано. Думайте об этом как о сборочном конвейере: по мере того, как виджет спускается по конвейеру, каждый шаг в этой линии изменяет виджет, так что к тому времени, когда он доберется до конечного пункта назначения, он становится полностью функционирующим виджетом.

Для получения дополнительной информации см. Концепции MDN Streams API .

Преобразуйте поток с помощью LineBreakTransformer

Давайте создадим класс LineBreakTransformer , который будет принимать поток и разбивать его на фрагменты на основе разрывов строк ( \r\n ). Классу нужны два метода: transform и flush . Метод transform вызывается каждый раз, когда поток получает новые данные. Он может либо поставить данные в очередь, либо сохранить их на будущее. Метод flush вызывается, когда поток закрывается, и он обрабатывает любые данные, которые еще не были обработаны.

В нашем методе transform мы добавим новые данные в container , а затем проверим, есть ли в container разрывы строк. Если они есть, разделите его на массив, а затем пройдитесь по строкам, вызвав controller.enqueue() для отправки проанализированных строк.

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));

Когда поток закрывается, мы просто удаляем все оставшиеся данные в контейнере с помощью enqueue .

script.js - LineBreakTransformer.flush()

// CODELAB: Flush the stream.
controller.enqueue(this.container);

Наконец, нам нужно передать входящий поток через новый LineBreakTransformer . Наш исходный входной поток передавался только через TextDecoderStream , поэтому нам нужно добавить дополнительный pipeThrough , чтобы передать его через наш новый 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()));

Попробуйте это

Теперь, когда вы нажимаете одну из кнопок micro:bit, напечатанные данные должны возвращаться в одной строке.

  1. Перезагрузите страницу.
  2. Нажмите кнопку Подключиться .
  3. В диалоговом окне выбора последовательного порта выберите устройство BBC micro:bit и нажмите « Подключиться» .
  4. Вы должны увидеть улыбку на светодиодной матрице micro:bit.
  5. Нажмите кнопки на micro:bit и убедитесь, что вы видите что-то вроде следующего:

6c2193880c748412.png

Преобразуйте поток с помощью JSONTransformer

Мы могли бы попытаться проанализировать строку в JSON в readLoop , но вместо этого давайте создадим очень простой преобразователь JSON, который преобразует данные в объект JSON. Если данные недействительны в формате JSON, просто верните то, что пришло.

script.js - JSONTransformer.transform

// CODELAB: Attempt to parse JSON content
try {
  controller.enqueue(JSON.parse(chunk));
} catch (e) {
  controller.enqueue(chunk);
}

Затем пропустите поток через JSONTransformer после того, как он прошел через LineBreakTransformer . Это позволяет нам сохранить наш JSONTransformer простым, поскольку мы знаем, что JSON всегда будет отправляться только в одной строке.

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()));

Попробуйте это

Теперь, когда вы нажимаете одну из кнопок micro:bit, вы должны увидеть напечатанный на странице [object Object] .

  1. Перезагрузите страницу.
  2. Нажмите кнопку Подключиться .
  3. В диалоговом окне выбора последовательного порта выберите устройство BBC micro:bit и нажмите « Подключиться» .
  4. Вы должны увидеть улыбку на светодиодной матрице micro:bit.
  5. Нажмите кнопки на micro:bit и убедитесь, что вы видите что-то вроде следующего:

Реагирование на нажатие кнопок

Чтобы реагировать на нажатия кнопок micro:bit, обновите readLoop чтобы проверить, являются ли полученные данные object со свойством button . Затем вызовите buttonPushed для обработки нажатия кнопки.

script.js - readLoop()

const { value, done } = await reader.read();
if (value && value.button) {
  buttonPushed(value);
} else {
  log.textContent += value + '\n';
}

При нажатии кнопки micro:bit изображение на светодиодной матрице должно измениться. Используйте следующий код для установки матрицы:

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();
  }
}

Попробуйте это

Теперь, когда вы нажимаете одну из кнопок micro:bit, светодиодная матрица должна измениться на счастливое или грустное лицо.

  1. Перезагрузите страницу.
  2. Нажмите кнопку Подключиться .
  3. В диалоговом окне выбора последовательного порта выберите устройство BBC micro:bit и нажмите « Подключиться» .
  4. Вы должны увидеть улыбку на светодиодной матрице micro:bits.
  5. Нажмите кнопки на micro:bit и убедитесь, что светодиодная матрица меняется.

8. Закрытие последовательного порта

Последний шаг — подключить функцию отключения, чтобы закрыть порт, когда пользователь закончит работу.

Закройте порт, когда пользователь нажимает кнопку «Подключить/Отключить».

Когда пользователь нажимает кнопку «Подключить / Отключить» , нам нужно закрыть соединение. Если порт уже открыт, вызовите disconnect() и обновите пользовательский интерфейс, чтобы указать, что страница больше не подключена к последовательному устройству.

script.js - clickConnect()

// CODELAB: Add disconnect code here.
if (port) {
  await disconnect();
  toggleUIConnected(false);
  return;
}

Закройте потоки и порт

В функции disconnect нам нужно закрыть входной поток, закрыть выходной поток и закрыть порт. Чтобы закрыть входной поток, вызовите reader.cancel() . Вызов cancel является асинхронным, поэтому нам нужно использовать await , чтобы дождаться его завершения:

script.js - disconnect()

// CODELAB: Close the input stream (reader).
if (reader) {
  await reader.cancel();
  await inputDone.catch(() => {});
  reader = null;
  inputDone = null;
}

Чтобы закрыть выходной поток, получите writer , вызовите close() и дождитесь закрытия объекта outputDone :

script.js - disconnect()

// CODELAB: Close the output stream.
if (outputStream) {
  await outputStream.getWriter().close();
  await outputDone;
  outputStream = null;
  outputDone = null;
}

Наконец, закройте последовательный порт и дождитесь его закрытия:

script.js - disconnect()

// CODELAB: Close the port.
await port.close();
port = null;

Попробуйте это

Теперь вы можете открывать и закрывать последовательный порт по своему желанию.

  1. Перезагрузите страницу.
  2. Нажмите кнопку Подключиться .
  3. В диалоговом окне выбора последовательного порта выберите устройство BBC micro:bit и нажмите « Подключиться».
  4. Вы должны увидеть улыбку на светодиодной матрице micro:bit.
  5. Нажимаем кнопку Отключить и убеждаемся, что светодиодная матрица погасла и в консоли нет ошибок.

9. Поздравления

Поздравляем! Вы успешно создали свое первое веб-приложение, использующее Web Serial API.

Следите за https://goo.gle/fugu-api-tracker, чтобы быть в курсе последних новостей об API Web Serial и обо всех других интересных новых веб-возможностях, над которыми работает команда Chrome.