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

1. Введение

Последнее обновление: 21.07.2020

Что вы построите

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

81167ab7c01d353d.png

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

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

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

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

2. О веб-последовательном API

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

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

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

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

3. Настройка

Получите код

Всё необходимое для этого практического занятия собрано в одном проекте Glitch.

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

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

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

Первым делом нужно проверить, поддерживается ли API Web Serial в текущем браузере. Для этого проверьте, присутствует ли 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. Если да, то он скрывает сообщение о том, что Web Serial не поддерживается.

Попробуйте!

  1. Загрузите страницу.
  2. Убедитесь, что на странице не отображается красный баннер, указывающий на неподдержку Web Serial.

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

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

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

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

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-to-serial и основным процессором.

Давайте также подключим кнопку "Подключиться" и настроим её так, чтобы при нажатии пользователем вызывался 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 равно true, порт закрыт или данные больше не поступают.

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

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. Далее нам нужно предоставить метод для отправки данных в поток. Приведенный ниже код получает объект writer из выходного потока, а затем использует 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 и введите writeToStream('console.log("yes")');

На странице должно быть напечатано что-то подобное:

a13187e7e6260f7f.png

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

Создайте строку матричной сетки.

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

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

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 ), и может применять произвольное преобразование перед окончательным потреблением. Представьте это как конвейер: по мере продвижения элемента по конвейеру каждый шаг изменяет его, так что к моменту достижения конечного пункта назначения он становится полностью функциональным.

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

Преобразуйте поток с помощью 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);
}

Далее, после обработки объекта LineBreakTransformer , передайте поток данных через JSONTransformer . Это позволяет нам сохранить простоту нашего 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, чтобы быть в курсе последних новостей о Web Serial API и всех других интересных новых веб-возможностях, над которыми работает команда Chrome.