Начало работы с Web Serial API

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

Что вы будете строить

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

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

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

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

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

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

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

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

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

Включить веб-последовательный API

Web Serial API в настоящее время находится в разработке и доступен только с пометкой. Вы должны включить флаг #enable-experimental-web-platform-features в chrome://flags .

Получить код

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

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

Проверьте, поддерживается ли Web Serial API.

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

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

script.js - DOMContentLoaded

// CODELAB: Add feature detection here.
if ('serial' in navigator) {
  const notSupported = document.getElementById('notSupported');
  notSupported.classList.add('hidden');
}

Это проверяет, поддерживается ли 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. На вкладке вы должны увидеть значок, указывающий, что вы подключились к последовательному устройству:

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

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

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

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

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

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

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

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

Добавьте событие просмотра на кнопки 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 и убедитесь, что он добавляет новый текст на страницу с информацией о нажатой кнопке.

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

Когда одна из кнопок micro:bit нажата, 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 и убедитесь, что вы видите что-то вроде следующего:

Преобразование потока с помощью 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 и убедитесь, что светодиодная матрица изменилась.

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

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

Когда пользователь нажимает кнопку Connect / Disconnect , нам нужно закрыть соединение. Если порт уже открыт, вызовите метод 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;
  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. Нажмите кнопку Disconnect и убедитесь, что светодиодная матрица погасла и в консоли нет ошибок.

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

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