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

Что вы узнаете
- Как открыть и закрыть веб-последовательный порт
- Как использовать цикл чтения для обработки данных из входного потока
- Как отправлять данные через поток записи
Что вам понадобится
- Микроплата BBC micro:bit с последней версией прошивки Espruino.
- Последняя версия Chrome (80 или более поздняя)
- Знание HTML, CSS, JavaScript и инструментов разработчика Chrome.
Для этой практической работы мы выбрали micro:bit, потому что он доступен по цене, имеет несколько входов (кнопки) и выходов (светодиодный дисплей 5x5), а также может предоставлять дополнительные входы и выходы. Подробную информацию о возможностях micro:bit можно найти на странице BBC micro:bit на сайте Espruino.
2. О веб-последовательном API
API Web Serial предоставляет веб-сайтам возможность считывать и записывать данные на последовательное устройство с помощью скриптов. API соединяет веб-пространство и физический мир, позволяя веб-сайтам взаимодействовать с последовательными устройствами, такими как микроконтроллеры и 3D-принтеры.
Существует множество примеров программного обеспечения для управления, созданного с использованием веб-технологий. Например:
В некоторых случаях эти веб-сайты взаимодействуют с устройством через собственное приложение-агент, которое устанавливается пользователем вручную. В других случаях приложение поставляется в виде упакованного собственного приложения через такой фреймворк, как Electron. В третьих случаях пользователю необходимо выполнить дополнительный шаг, например, скопировать скомпилированное приложение на устройство с помощью USB-накопителя.
Пользовательский опыт можно улучшить, обеспечив прямую связь между сайтом и управляемым им устройством.
3. Настройка
Получите код
Всё необходимое для этого практического занятия собрано в одном проекте Glitch.
- Откройте новую вкладку браузера и перейдите по ссылке https://web-serial-codelab-start.glitch.me/ .
- Нажмите на ссылку Remix Glitch , чтобы создать свою собственную версию стартового проекта.
- Нажмите кнопку «Показать» , а затем выберите «В новом окне» , чтобы увидеть свой код в действии.
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 не поддерживается.
Попробуйте!
- Загрузите страницу.
- Убедитесь, что на странице не отображается красный баннер, указывающий на неподдержку 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.
- Перезагрузите страницу.
- Нажмите кнопку «Подключиться» .
- В диалоговом окне выбора последовательного порта выберите устройство BBC micro:bit и нажмите «Подключить» .
- На вкладке вы должны увидеть значок, указывающий на подключение к последовательному устройству:

Настройте входной поток для приема данных с последовательного порта.
После установления соединения нам необходимо настроить входной поток и считыватель для чтения данных с устройства. Сначала мы получим читаемый поток с порта, вызвав метод 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;
}
}
Попробуйте!
Теперь наш проект может подключаться к устройству и добавлять любые данные, полученные от устройства, в элемент журнала.
- Перезагрузите страницу.
- Нажмите кнопку «Подключиться» .
- В диалоговом окне выбора последовательного порта выберите устройство BBC micro:bit и нажмите «Подключить» .
- Вы должны увидеть логотип Espruino:

Настройте выходной поток для отправки данных на последовательный порт.
Последовательная связь обычно является двунаправленной. Помимо приема данных с последовательного порта, мы также хотим отправлять данные на этот порт. Как и в случае с входным потоком, мы будем отправлять текст только через выходной поток на 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. Давайте проверим, можем ли мы корректно отправить команду:
- Перезагрузите страницу.
- Нажмите кнопку «Подключиться» .
- В диалоговом окне выбора последовательного порта выберите устройство BBC micro:bit и нажмите «Подключить» .
- Откройте вкладку «Консоль» в инструментах разработчика Chrome и введите
writeToStream('console.log("yes")');
На странице должно быть напечатано что-то подобное:

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, она будет отправлять смайлик. Нажатие на флажки обновит изображение на светодиодной матрице.
- Перезагрузите страницу.
- Нажмите кнопку «Подключиться» .
- В диалоговом окне выбора последовательного порта выберите устройство BBC micro:bit и нажмите «Подключить» .
- На светодиодной матрице micro:bit должна отобразиться улыбка.
- Нарисуйте другой узор на светодиодной матрице, изменив значения флажков.
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 добавит на страницу текст, указывающий, какая кнопка была нажата. Скорее всего, каждый символ будет на отдельной строке.
- Перезагрузите страницу.
- Нажмите кнопку «Подключиться» .
- В диалоговом окне выбора последовательного порта выберите устройство BBC micro:bit и нажмите «Подключить» .
- На светодиодной матрице micro:bits должна отобразиться улыбка.
- Нажмите кнопки на 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, данные должны выводиться в одной строке.
- Перезагрузите страницу.
- Нажмите кнопку «Подключиться» .
- В диалоговом окне выбора последовательного порта выберите устройство BBC micro:bit и нажмите «Подключить» .
- На светодиодной матрице micro:bit должна отобразиться улыбка.
- Нажмите кнопки на micro:bit и убедитесь, что вы видите что-то подобное следующему:

Преобразуйте поток с помощью 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] .
- Перезагрузите страницу.
- Нажмите кнопку «Подключиться» .
- В диалоговом окне выбора последовательного порта выберите устройство BBC micro:bit и нажмите «Подключить» .
- На светодиодной матрице micro:bit должна отобразиться улыбка.
- Нажмите кнопки на 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, светодиодная матрица должна изменить цвет либо на улыбающееся, либо на грустное лицо.
- Перезагрузите страницу.
- Нажмите кнопку «Подключиться» .
- В диалоговом окне выбора последовательного порта выберите устройство BBC micro:bit и нажмите «Подключить» .
- На светодиодной матрице micro:bits должна отобразиться улыбка.
- Нажмите кнопки на 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;
Попробуйте!
Теперь вы можете открывать и закрывать последовательный порт по своему желанию.
- Перезагрузите страницу.
- Нажмите кнопку «Подключиться» .
- В диалоговом окне выбора последовательного порта выберите устройство BBC micro:bit и нажмите «Подключить».
- Вы должны увидеть улыбку на светодиодной матрице micro:bit.
- Нажмите кнопку «Отключить» и убедитесь, что светодиодная матрица погасла и в консоли нет ошибок.
9. Поздравляем!
Поздравляем! Вы успешно создали своё первое веб-приложение, использующее Web Serial API.
Следите за обновлениями на https://goo.gle/fugu-api-tracker, чтобы быть в курсе последних новостей о Web Serial API и всех других интересных новых веб-возможностях, над которыми работает команда Chrome.