1. Introdução
Última atualização:21/07/2020
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.
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
- Uma placa BBC micro:bit com o firmware Espruino mais recente
- Uma versão recente do Chrome (80 ou mais recente)
- Conhecimento sobre HTML, CSS, JavaScript e Chrome DevTools.
Escolhemos 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 mais entradas e saídas. Consulte a página micro:bit da BBC (em inglês) no site de Espruino para saber mais detalhes sobre o que o micro:bit é capaz.
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 software de controle sendo construído usando tecnologia web. Exemplo:
- Arduino Create (em inglês)
- Configurador do Betaflight
- Ambiente de desenvolvimento integrado da Espruino
- MakeCode
Em alguns casos, esses sites se comunicam com o dispositivo por meio de um aplicativo de agente nativo que é 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 adicional, como copiar um aplicativo compilado para o dispositivo com um pen drive.
A experiência do usuário pode ser melhorada fornecendo uma comunicação direta entre o site e o dispositivo que ele está controlando.
3. Etapas da configuração
Buscar o código
Colocamos tudo o que você precisa para este codelab em um projeto Glitch.
- Abra uma nova guia do navegador e acesse https://web-serial-codelab-start.glitch.me/.
- Clique no link Remix Glitch para criar sua própria versão do projeto inicial.
- Clique no botão Mostrar e escolha Em uma nova janela para ver o código em ação.
4. Abrir uma conexão serial
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, 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 vai ocultar o banner que informa que o serial da Web não é compatível.
Faça um teste
- Carregue a página.
- 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 evita que a IU bloqueie ao aguardar entradas, mas também é importante porque os dados seriais podem ser recebidos pela página da Web a qualquer momento, e precisamos de uma maneira de escutá-los.
Como um computador pode ter vários dispositivos seriais, quando o navegador tenta solicitar uma porta, ele pede 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 ao usuário a qual dispositivo ele quer se 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 seu 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. Clicar no botão Conectar solicita que o usuário selecione o dispositivo serial ao qual se conectar e, em seguida, se conecta ao micro:bit.
- Recarregue a página.
- Clique no botão Conectar.
- Na caixa de diálogo do seletor de porta serial, selecione o dispositivo micro:bit BBC e clique em Conectar.
- Na guia, você verá um ícone indicando que se conectou a um dispositivo serial:
Configurar um stream 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 de entrada.
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;
}
}
Faça um teste
Nosso projeto agora pode se conectar ao dispositivo e anexar todos os dados recebidos do dispositivo ao elemento de registro.
- Recarregue a página.
- Clique no botão Conectar.
- Na caixa de diálogo do seletor de porta serial, selecione o dispositivo micro:bit BBC e clique em Conectar.
- Você verá o logotipo da Espruino:
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, o texto só será enviado pelo stream 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 instruir o micro:bit a 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:
- Recarregue a página.
- Clique no botão Conectar.
- Na caixa de diálogo do seletor de porta serial, selecione o dispositivo micro:bit BBC e clique em Conectar.
- Abra a guia Console no Chrome DevTools e digite
writeToStream('console.log("yes")');
.
A página exibirá algo parecido com isto:
5. Controlar a matriz de LED
Criar a string de grade da 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 as alterações 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. Clique nas caixas de seleção para atualizar a tela na matriz de LED.
- Recarregue a página.
- Clique no botão Conectar.
- Na caixa de diálogo do seletor de porta serial, selecione o dispositivo micro:bit BBC e clique em Conectar.
- Um sorriso será mostrado na matriz de LED micro:bit.
- 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 escutar os dois botões, tornaremos nossa função genérica e faremos com que ela mostre 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. O mais provável é que cada caractere esteja em sua própria linha.
- Recarregue a página.
- Clique no botão Conectar.
- Na caixa de diálogo do seletor de porta serial, selecione o dispositivo micro:bit BBC e clique em Conectar.
- Um sorriso será mostrado na matriz de LED de micro:bits.
- Pressione os botões no micro:bit e verifique se ele anexa um novo texto à página com os 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 micro:bit é pressionado, o micro:bit envia dados para a porta serial por um stream. 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 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 aplicar uma transformação arbitrária antes de ser finalmente consumida. Pense nisso como uma linha de montagem: à medida que um widget desce da linha, cada etapa da linha modifica o widget, de modo que, no momento em que ele chega ao destino final, ele é um widget totalmente funcional.
Para mais informações, consulte os conceitos da API Streams do MDN.
Transformar o stream com LineBreakTransformer
Vamos criar uma classe LineBreakTransformer
, que dividirá um stream 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 enfileirar os dados ou salvá-los para mais tarde. O método flush
é chamado quando o stream é fechado e processa os dados que ainda não foram processados.
No nosso método transform
, adicionaremos novos dados ao container
e, em seguida, verificaremos se há quebras de linha em container
. Se houver, divida-o em uma matriz e faça iterações nas 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.
- Recarregue a página.
- Clique no botão Conectar.
- Na caixa de diálogo do seletor de porta serial, selecione o dispositivo micro:bit BBC e clique em Conectar.
- Um sorriso será mostrado na matriz de LED micro:bit.
- Pressione os botões no micro:bit e verifique se aparece algo parecido com o seguinte:
Transformar o stream com JSONTransformer
Podemos tentar analisar a string em JSON no readLoop
, mas, em vez disso, vamos criar um transformador JSON muito simples que 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, direcione o stream pelo JSONTransformer
, depois que ele passar pelo LineBreakTransformer
. Isso permite manter o JSONTransformer
simples, já que sabemos que o JSON será enviado apenas 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 micro:bit, o [object Object]
vai aparecer na página.
- Recarregue a página.
- Clique no botão Conectar.
- Na caixa de diálogo do seletor de porta serial, selecione o dispositivo micro:bit BBC e clique em Conectar.
- Um sorriso será mostrado na matriz de LED micro:bit.
- 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, ao pressionar um dos botões micro:bit, a matriz de LED vai mudar para um rosto feliz ou triste.
- Recarregue a página.
- Clique no botão Conectar.
- Na caixa de diálogo do seletor de porta serial, selecione o dispositivo micro:bit BBC e clique em Conectar.
- Um sorriso será exibido na matriz de LED de micro:bits.
- 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, é necessário encerrar 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 os streams 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 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.catch(() => {});
reader = null;
inputDone = null;
}
Para fechar o stream de saída, receba um writer
, chame close()
e aguarde o objeto outputDone
ser 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;
Faça um teste
Agora, você pode abrir e fechar a porta serial à vontade.
- Recarregue a página.
- Clique no botão Conectar.
- Na caixa de diálogo do seletor de porta serial, selecione o dispositivo micro:bit BBC e clique em Conectar.
- Um sorriso será mostrado na matriz de LED micro:bit
- 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.