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