Introdução à API Web Serial

1. Introdução

Última atualização: 19/09/2022

O que você vai criar

Neste codelab, você vai criar uma página da Web que usa a API Web Serial para interagir com uma placa BBC micro:bit e mostrar imagens na matriz de LED 5x5. Você vai aprender sobre a API Web Serial e como usar streams legíveis, graváveis e de transformação para se comunicar com dispositivos seriais pelo navegador.

67543f4caaaca5de.png

O que você vai aprender

  • Como abrir e fechar uma porta serial da Web
  • Como usar um loop de leitura para processar dados de um stream de entrada.
  • Como enviar dados por meio de um fluxo de gravação

O que é necessário

Escolhemos usar o micro:bit v1 para este codelab porque ele é acessível, oferece algumas entradas (botões) e saídas (tela de LED 5x5) e pode fornecer entradas e saídas adicionais. Consulte a página da BBC micro:bit no site da Espruino para saber mais sobre o que o micro:bit pode fazer.

2. Sobre a API Web Serial

A API Web Serial permite que os sites leiam e gravem em um dispositivo serial com scripts. A API conecta a Web e o mundo físico, permitindo que sites se comuniquem com dispositivos seriais, como microcontroladores e impressoras 3D.

Há muitos exemplos de softwares de controle criados com tecnologia da Web. Exemplo:

Em alguns casos, esses sites se comunicam com o dispositivo por meio de um aplicativo de agente nativo instalado manualmente pelo usuário. Em outros, o aplicativo é entregue em um aplicativo nativo empacotado usando um framework como o Electron. Em outros casos, o usuário precisa realizar uma etapa extra, como copiar um aplicativo compilado para o dispositivo com um pen drive USB.

A experiência do usuário pode ser melhorada com a comunicação direta entre o site e o dispositivo que ele controla.

3. Etapas da configuração

Buscar o código

Colocamos tudo o que você precisa para este codelab em um projeto Glitch.

  1. Abra uma nova guia do navegador e acesse https://web-serial-codelab-start.glitch.me/.
  2. Clique no link Remix Glitch para criar sua própria versão do projeto inicial.
  3. Clique no botão Show e escolha In a New Window para conferir o código em ação.

4. Abrir uma conexão serial

Verificar se a API Web Serial tem suporte

A primeira coisa a fazer é verificar se a API Serial da Web tem suporte no navegador atual. Para fazer isso, verifique se serial está em navigator.

No evento DOMContentLoaded, adicione o seguinte código ao seu projeto:

script.js - DOMContentLoaded

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

Ela verifica se o serial da Web é compatível. Se for, esse código oculta o banner que informa que a Web Serial não tem suporte.

Faça um teste

  1. Carregue a página.
  2. Verifique se a página não mostra um banner vermelho informando que o Web Serial não é compatível.

Abrir a porta serial

Em seguida, precisamos abrir a porta serial. Como a maioria das outras APIs modernas, a API Web Serial é assíncrona. Isso impede que a interface seja bloqueada ao aguardar a entrada, mas também é importante porque os dados seriados podem ser recebidos pela página da Web a qualquer momento e precisamos de uma maneira de detectá-los.

Como um computador pode ter vários dispositivos seriais, quando o navegador tenta solicitar uma porta, ele solicita que o usuário escolha o dispositivo para se conectar.

Adicione o seguinte código ao projeto:

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

A chamada requestPort solicita ao usuário qual dispositivo ele quer conectar. Chamar port.open abre a porta. Também precisamos fornecer a velocidade com que queremos nos comunicar com o dispositivo serial. O micro:bit da BBC usa uma conexão de 9.600 Baud entre o chip USB para serial e o processador principal.

Também vamos conectar o botão de conexão e fazer com que ele chame connect() quando o usuário clicar nele.

Adicione o seguinte código ao projeto:

script.js - clickConnect()

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

Faça um teste

Agora nosso projeto tem o mínimo necessário para começar. Ao clicar no botão Connect, o usuário seleciona o dispositivo serial para se conectar e, em seguida, se conecta ao micro:bit.

  1. Recarregue a página.
  2. Clique no botão Conectar.
  3. Na caixa de diálogo do seletor de porta serial, selecione o dispositivo micro:bit BBC e clique em Conectar.
  4. Na guia, você verá um ícone indicando que se conectou a um dispositivo serial:

e695daf2277cd3a2.png

Configurar um fluxo de entrada para detectar dados da porta serial

Depois que a conexão for estabelecida, precisamos configurar um fluxo de entrada e um leitor para ler os dados do dispositivo. Primeiro, receberemos o stream legível da porta chamando port.readable. Como sabemos que receberemos o texto do dispositivo, vamos encaminhá-lo por um decodificador de texto. Em seguida, obteremos um leitor e iniciaremos a repetição de leitura.

Adicione o seguinte código ao seu projeto:

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

O loop de leitura é uma função assíncrona que é executada em uma repetição e aguarda conteúdo sem bloquear a linha de execução principal. Quando novos dados chegam, o leitor retorna duas propriedades: value e um booleano done. Se done for verdadeiro, a porta foi fechada ou não há mais dados chegando.

Adicione o seguinte código ao projeto:

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

Faça um teste

Agora nosso projeto pode se conectar ao dispositivo e anexar todos os dados recebidos dele ao elemento de registro.

  1. Recarregue a página.
  2. Clique no botão Conectar.
  3. Na caixa de diálogo Serial Port chooser, selecione o dispositivo BBC micro:bit e clique em Connect.
  4. O logotipo do Espruino vai aparecer:

dd52b5c37fc4b393.png

Configurar um stream de saída para enviar dados para a porta serial

A comunicação serial geralmente é bidirecional. Além de receber dados da porta serial, também queremos enviar dados para a porta serial. Assim como no fluxo de entrada, vamos enviar apenas texto pelo fluxo de saída para o micro:bit.

Primeiro, crie um stream de codificador de texto e canalize-o para 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;

Quando conectada por serial com o firmware Espruino, a placa micro:bit da BBC funciona como um loop de leitura-avaliação-impressão (REPL, na sigla em inglês) JavaScript, semelhante ao que você recebe em um shell do Node.js. Em seguida, precisamos fornecer um método para enviar dados ao stream. O código abaixo recebe um gravador do stream de saída e usa write para enviar cada linha. Cada linha enviada inclui um caractere de nova linha (\n) para informar ao micro:bit que ele precisa avaliar o comando enviado.

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

Para colocar o sistema em um estado conhecido e impedir que ele retorne os caracteres que enviamos, precisamos enviar CTRL+C e desativar o eco.

script.js - connect()

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

Faça um teste

Nosso projeto agora pode enviar e receber dados do micro:bit. Vamos verificar se podemos enviar um comando corretamente:

  1. Recarregue a página.
  2. Clique no botão Conectar.
  3. Na caixa de diálogo do seletor de porta serial, selecione o dispositivo micro:bit BBC e clique em Conectar.
  4. Abra a guia Console no Chrome DevTools e digite writeToStream('console.log("yes")');.

A página exibirá algo parecido com isto:

15e2df0064b5de28.png

5. Controlar a matriz de LED

Criar a string de grade de matriz

Para controlar a matriz de LED no micro:bit, precisamos chamar show(). Esse método mostra gráficos na tela LED 5x5 integrada. Ela usa um número binário ou uma string.

Iteraremos as caixas de seleção e geraremos uma matriz de 1s e 0s indicando qual está marcada e qual não está. Em seguida, precisamos inverter a matriz, porque a ordem das caixas de seleção é o oposto da ordem dos LEDs na matriz. Em seguida, convertemos a matriz em uma string e criamos o comando para enviar ao 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('')})`);

Marque as caixas de seleção para atualizar a matriz

Em seguida, precisamos detectar mudanças nas caixas de seleção e, se elas mudarem, enviar essas informações para o micro:bit. No código de detecção de recursos (// CODELAB: Add feature detection here.), adicione a seguinte linha:

script.js - DOMContentLoaded

initCheckboxes();

Também vamos redefinir a grade quando o micro:bit for conectado pela primeira vez, para que ele mostre uma carinha feliz. A função drawGrid() já foi fornecida. Essa função funciona de maneira semelhante a sendGrid(). ele usa uma matriz de 1s e 0s e marca as caixas de seleção conforme apropriado.

script.js - clickConnect()

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

Faça um teste

Agora, quando a página abrir uma conexão com o micro:bit, ela vai enviar uma carinha feliz. Clicar nas caixas de seleção atualiza a exibição na matriz de LED.

  1. Recarregue a página.
  2. Clique no botão Conectar.
  3. Na caixa de diálogo Serial Port chooser, selecione o dispositivo BBC micro:bit e clique em Connect.
  4. Você vai ver um sorriso mostrado na matriz de LED do micro:bit.
  5. Desenhe um padrão diferente na matriz de LED mudando as caixas de seleção.

6. Conectar os botões micro:bit

Adicionar um evento de exibição aos botões micro:bit

Existem dois botões no micro:bit, um em cada lado da matriz de LED. O Espruino fornece uma função setWatch que envia um evento/callback quando o botão é pressionado. Como queremos detectar os dois botões, vamos tornar nossa função genérica e imprimir os detalhes do evento.

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

Em seguida, precisamos conectar os dois botões (chamados BTN1 e BTN2 na placa micro:bit) toda vez que a porta serial for conectada ao dispositivo.

script.js - clickConnect()

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

Faça um teste

Além de mostrar um rosto feliz quando conectado, pressionar um dos botões no micro:bit adiciona um texto à página indicando qual botão foi pressionado. Provavelmente, cada caractere estará em uma linha.

  1. Recarregue a página.
  2. Clique no botão Conectar.
  3. Na caixa de diálogo do seletor de porta serial, selecione o dispositivo micro:bit BBC e clique em Conectar.
  4. Um sorriso vai aparecer na matriz de LED do micro:bit.
  5. Pressione os botões no micro:bit e verifique se ele anexa um novo texto à página com detalhes do botão pressionado.

7. Usar um fluxo de transformação para analisar os dados de entrada

Processamento básico de stream

Quando um dos botões do micro:bit é pressionado, ele envia dados para a porta serial por um fluxo. Os streams são muito úteis, mas também podem ser um desafio, porque você não terá necessariamente todos os dados de uma vez e eles podem ser arbitrariamente divididos.

No momento, o app imprime o stream de entrada assim que ele chega (em readLoop). Na maioria dos casos, cada caractere está na própria linha, mas isso não é muito útil. O ideal é que o stream seja analisado em linhas individuais, e cada mensagem mostrada como uma linha própria.

Como transformar streams com TransformStream

Para isso, podemos usar um stream de transformação (TransformStream), que permite analisar o stream de entrada e retornar os dados analisados. Um fluxo de transformação pode ficar entre a origem do fluxo (neste caso, o micro:bit) e o que consome o fluxo (neste caso, readLoop) e pode aplicar uma transformação arbitrária antes de ser finalmente consumido. Pense nisso como uma linha de montagem: à medida que um widget passa pela linha, cada etapa na linha modifica o widget, de modo que, quando ele chega ao destino final, ele está totalmente funcional.

Para mais informações, consulte os conceitos da API Streams do MDN.

Transformar o fluxo com LineBreakTransformer

Vamos criar uma classe LineBreakTransformer, que vai receber um stream e dividi-lo com base em quebras de linha (\r\n). A classe precisa de dois métodos, transform e flush. O método transform é chamado sempre que novos dados são recebidos pelo stream. Ele pode enfileirar os dados ou salvá-los para mais tarde. O método flush é chamado quando o stream é fechado e processa todos os dados que ainda não foram processados.

No método transform, vamos adicionar novos dados a container e verificar se há quebras de linha em container. Se houver, divida em uma matriz e itere pelas linhas, chamando controller.enqueue() para enviar as linhas analisadas.

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

Quando o stream é fechado, simplesmente limpamos todos os dados restantes no contêiner usando enqueue.

script.js - LineBreakTransformer.flush()

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

Por fim, precisamos canalizar o stream de entrada pelo novo LineBreakTransformer. Nosso stream de entrada original só passou por TextDecoderStream, então precisamos adicionar mais pipeThrough para canalizá-lo pelo novo 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()));

Faça um teste

Agora, quando você pressionar um dos botões micro:bit, os dados impressos deverão ser retornados em uma única linha.

  1. Recarregue a página.
  2. Clique no botão Conectar.
  3. Na caixa de diálogo Serial Port chooser, selecione o dispositivo BBC micro:bit e clique em Connect.
  4. Você vai ver um sorriso mostrado na matriz de LED do micro:bit.
  5. Pressione os botões no micro:bit e verifique se aparece algo parecido com o seguinte:

eead3553d29ee581.png

Transformar o stream com JSONTransformer

Poderíamos tentar analisar a string em JSON no readLoop, mas vamos criar um transformador JSON muito simples que vai transformar os dados em um objeto JSON. Se os dados não forem um JSON válido, basta retornar o que veio.

script.js - JSONTransformer.transform

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

Em seguida, transmita o fluxo pelo JSONTransformer, depois que ele passar pelo LineBreakTransformer. Isso permite manter o JSONTransformer simples, já que sabemos que o JSON só será enviado em uma única linha.

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

Faça um teste

Agora, quando você pressionar um dos botões do micro:bit, o [object Object] vai aparecer na página.

  1. Recarregue a página.
  2. Clique no botão Conectar.
  3. Na caixa de diálogo Serial Port chooser, selecione o dispositivo BBC micro:bit e clique em Connect.
  4. Um sorriso será mostrado na matriz de LED micro:bit.
  5. Pressione os botões no micro:bit e verifique se aparece algo parecido com o seguinte:

Responder ao pressionamento de botão

Para responder ao pressionamento do botão micro:bit, atualize readLoop para verificar se os dados recebidos são um object com uma propriedade button. Em seguida, chame buttonPushed para processar o pressionamento do botão.

script.js - readLoop()

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

Quando um botão micro:bit é pressionado, a tela na matriz de LED precisa mudar. Use o seguinte código para definir a matriz:

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

Faça um teste

Agora, quando você pressionar um dos botões do micro:bit, a matriz de LED vai mudar para um rosto feliz ou triste.

  1. Recarregue a página.
  2. Clique no botão Conectar.
  3. Na caixa de diálogo do seletor de porta serial, selecione o dispositivo micro:bit BBC e clique em Conectar.
  4. Um sorriso será mostrado na matriz de LED de micro:bits.
  5. Pressione os botões no micro:bit e verifique se a matriz de LED muda.

8. Como fechar a porta serial

A etapa final é conectar a funcionalidade de desconexão para fechar a porta quando o usuário terminar de usá-la.

Fechar a porta quando o usuário clicar no botão "Conectar/desconectar"

Quando o usuário clica no botão Conectar/Desconectar, precisamos fechar a conexão. Se a porta já estiver aberta, chame disconnect() e atualize a interface para indicar que a página não está mais conectada ao dispositivo serial.

script.js - clickConnect()

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

Fechar as transmissões e a porta

Na função disconnect, precisamos fechar o stream de entrada e o de saída e fechar a porta. Para fechar o fluxo de entrada, chame reader.cancel(). A chamada para cancel é assíncrona, então precisamos usar await para aguardar a conclusão:

script.js - disconnect()

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

Para fechar o stream de saída, receba um writer, chame close() e aguarde o fechamento do objeto outputDone:

script.js - disconnect()

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

Por fim, feche a porta serial e aguarde o fechamento:

script.js - disconnect()

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

Faça um teste

Agora, você pode abrir e fechar a porta serial à vontade.

  1. Recarregue a página.
  2. Clique no botão Conectar.
  3. Na caixa de diálogo do seletor de porta serial, selecione o dispositivo BBC micro:bit e clique em Conectar.
  4. Um sorriso vai aparecer na matriz de LEDs do micro:bit.
  5. Pressione o botão Desconectar e verifique se a matriz de LED apagará e se não há erros no console.

9. Parabéns

Parabéns! Você criou seu primeiro app da Web que usa a API Web Serial.

Fique de olho em https://goo.gle/fugu-api-tracker (link em inglês) para conhecer as novidades sobre a API Web Serial e todos os outros novos recursos da Web interessantes em que a equipe do Chrome está trabalhando.