Primeiros passos com a API Web Serial

Última atualização: 08/11/2019

O que você vai criar

Neste codelab, você 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 dela. Você 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.

O que você 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 um stream de gravação

O que é necessário

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

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

Há muitos exemplos de software de controle sendo criados com a 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 casos, o aplicativo é entregue em um aplicativo nativo empacotado por meio de um framework como o Electron. Em outros casos, o usuário precisa realizar uma etapa adicional, como copiar um aplicativo compilado para o dispositivo com um pen drive.

É possível melhorar a experiência do usuário fornecendo comunicação direta entre o site e o dispositivo que ele controla.

Ativar a API Web Serial

A API Web Serial está em desenvolvimento e só está disponível por trás de uma sinalização. Ative a sinalização #enable-experimental-web-platform-features em chrome://flags.

Buscar o código

Colocamos tudo o que você precisa para este codelab em um projeto do 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 Mostrar e escolha Em uma nova janela para ver seu código em ação.

Verificar se a API Web Serial é compatível

A primeira coisa a fazer é verificar se a API Web Serial é compatível com o navegador atual. Para fazer isso, confira se serial está em navigator.

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

script.js - DOMContentLoaded

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

Isso verifica se o serial da Web é compatível. Se estiver, esse código oculta o banner que informa que o serial da Web não é compatível.

Testar

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

Abra 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 IU seja bloqueada ao aguardar entradas, mas também é importante porque os dados seriais podem ser recebidos pela página da Web a qualquer momento. Além disso, precisamos de uma maneira de ouvi-los.

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

Adicione o seguinte código ao seu 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 que o usuário a qual dispositivo ele quer se conectar. Chamar port.open abre a porta. Também precisamos oferecer a velocidade com que queremos nos comunicar com o dispositivo serial. O micro:bit BBC usa uma conexão baud de 9600 entre o chip USB-para-serial e o processador principal.

Vamos também 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 seu projeto:

script.js - clickConnect()

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

Testar

Nosso projeto agora tem o mínimo para começar. Clicar no botão Conectar solicita que o usuário selecione o dispositivo serial para se conectar e, em seguida, se conecte 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 BBC micro:bit e clique em Conectar.
  4. Na guia, você verá um ícone que indica que você se conectou a um dispositivo serial:

Configure um stream de entrada para detectar dados da porta serial

Depois que a conexão for estabelecida, será necessário configurar um fluxo de entrada e um leitor para ler os dados do dispositivo. Primeiro, chamaremos o stream legível da porta chamando port.readable. Como sabemos que vamos receber o texto de volta do dispositivo, vamos transmiti-lo por um decodificador de texto. Em seguida, temos um leitor e iniciamos o loop 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();

A repetição de leitura é uma função assíncrona que é executada em um loop e aguarda o 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.

Adicione o seguinte código ao seu 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;
  }
}

Testar

Nosso projeto agora pode se conectar ao dispositivo e anexará todos os dados recebidos dele ao elemento do registro.

  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. Você verá o logotipo do Espruino:

Configure um stream de saída para enviar dados à porta serial

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

Primeiro, crie uma transmissão do codificador de texto e direcione a transmissã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 conectado por serial com o firmware do Espruino, a placa micro:bit da BBC funciona como um loop de leitura-avaliação-impressão (REPL, na sigla em inglês) do JavaScript, semelhante ao que você recebe em um shell 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 fazer com que o micro:bit avalie 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 evitar que ele retorne os caracteres que enviamos, precisamos enviar um CTRL-C e desativar o eco.

script.js - connect()

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

Testar

Agora nosso projeto 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 BBC micro:bit e clique em Conectar.
  4. Abra a guia Console no Chrome DevTools e digite
    writeToStream('console.log("yes")');.

Você verá algo como exibido na página:

Criar a string de grade da matriz

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

Vamos iterar as caixas de seleção e gerar uma matriz de 1s e 0s, indicando o que está marcado e qual não está. Em seguida, precisamos inverter a matriz, porque a ordem das nossas 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('')})`);

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

Em seguida, precisamos detectar mudanças nas caixas de seleção e, se elas forem alteradas, 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();

Vamos redefinir a grade quando o micro:bit estiver conectado pela primeira vez, para que um rosto feliz apareça. A função drawGrid() já foi fornecida. Essa função funciona de maneira semelhante a sendGrid(). Ela 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();

Testar

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

  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. Você verá um sorriso na matriz de LED micro:bit.
  5. Desenhe um padrão diferente na matriz LED, alterando as caixas de seleção.

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

Há dois botões na micro:bit, um em cada lado da matriz de LED. O Espruino oferece uma função setWatch que envia um evento/callback ao pressionar o botão. Como queremos ouvir 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 de BTN1 e BTN2 na placa micro:bit) sempre que a porta serial estiver conectada ao dispositivo.

script.js - clickConnect()

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

Testar

Além de mostrar um rosto feliz quando conectado, pressionar qualquer um dos botões no micro:bit adicionará um texto à página indicando qual botão foi pressionado. É provável que cada caractere esteja em uma linha separada.

  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. Você verá um sorriso na matriz de LED micro:bits.
  5. Pressione os botões do micro:bit e verifique se ela anexa um novo texto à página com detalhes do botão pressionado.

Processamento básico de streams

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

No momento, o app imprime o stream de entrada quando 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 seja mostrada como sua própria linha.

Como transformar streams com TransformStream

Para isso, podemos usar um fluxo de transformação (TransformStream), que possibilita analisar o fluxo de entrada e retornar dados analisados. Um stream de transformação pode ficar entre a origem do stream (neste caso, o micro:bit) e o que estiver consumindo o stream (neste caso, readLoop) e pode aplicar uma transformação arbitrária antes de ser consumida. Pense nele como uma linha de montagem: quando um widget desce a linha, cada etapa na linha modifica o widget para que, no momento em que chegar ao destino final, ele seja um widget totalmente funcional.

Para mais informações, consulte Conceitos da API Streams MDN&.

Transforme o stream com LineBreakTransformer

Vamos criar uma classe LineBreakTransformer, que pegará um fluxo e a dividirá com base nas 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 colocar os dados em fila 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, adicionaremos novos dados ao container e, em seguida, verificaremos se há quebras de linha em container. Se houver, divida-a em uma matriz e itere as 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, basta limpar todos os dados restantes no contêiner usando enqueue.

script.js - LineBreakTransformer.flush()

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

Por fim, precisamos processar o fluxo de entrada usando o novo LineBreakTransformer. Nosso stream de entrada original era transmitido apenas por uma TextDecoderStream. Portanto, precisamos adicionar mais um pipeThrough para transmiti-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()));

Testar

Agora, quando você pressionar um dos botões de micro:bit, os dados impressos serão retornados em uma única 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 BBC micro:bit e clique em Conectar.
  4. Você verá um sorriso na matriz de LED micro:bit.
  5. Pressione os botões do micro:bit e verifique se aparece algo semelhante a isto:

Transforme o stream com JSONTransformer

Poderíamos tentar analisar a string como JSON no readLoop. Em vez disso, vamos criar um transformador JSON muito simples que transformará os dados em um objeto JSON. Se os dados não forem válidos, 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, direcione o stream pelo JSONTransformer, depois de passar pelo LineBreakTransformer. Isso permite manter nossa JSONTransformer simples, porque 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()));

Testar

Agora, ao pressionar um dos botões de micro:bit, você verá [object Object] impresso na página.

  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. Você verá um sorriso na matriz de LED micro:bit.
  5. Pressione os botões do micro:bit e verifique se aparece algo assim:

Responder a pressionamentos de botão

Para responder ao pressionamento do botão micro:bit, atualize readLoop para verificar se os dados recebidos são object com uma propriedade button. Em seguida, chame o buttonPushed para processar a ação de pressionar o 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 for pressionado, a tela na matriz de LED 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();
  }
}

Testar

Agora, quando você pressionar um dos botões de micro:bit, a matriz de LED mudará para um rosto feliz ou um rosto 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 BBC micro:bit e clique em Conectar.
  4. Você verá um sorriso na matriz de LED micro:bits.
  5. Pressione os botões do micro:bit e verifique se a matriz de LED muda.

A etapa final é conectar o recurso de desconexão para fechar a porta quando o usuário terminar.

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 IU 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 os streams e a porta

Na função disconnect, precisamos fechar o stream de entrada, o stream de saída e a porta. Para fechar o stream 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;
  reader = null;
  inputDone = null;
}

Para fechar o stream de saída, receba um writer, chame close() e aguarde até que o objeto outputDone seja fechado:

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;

Testar

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 micro:bit da BBC e clique em Conectar.
  4. Você verá um sorriso na matriz de LED micro:bit
  5. Pressione o botão Desconectar e verifique se a matriz de LED é desligada e se não há erros no console.

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

Fique de olho no site https://goo.gle/fugu-api-tracker para saber as últimas informações sobre a API Web Serial e todos os outros novos recursos incríveis da Web que a equipe do Chrome está desenvolvendo.