Ú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
- Uma placa BBC micro:bit com o upgrade de Espruino mais recente
- Uma versão recente do Chrome (78 ou mais recente)
- Conhecimento sobre HTML, CSS, JavaScript e Chrome DevTools
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:
- Arduino Create
- Configurador Betaflight
- Ambiente de desenvolvimento integrado do Espruino (em inglês)
- MakeCode (link em inglês)
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.
- 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 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
- Carregue a página.
- 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.
- Recarregue a página.
- Clique no botão Conectar.
- Na caixa de diálogo do seletor de porta serial, selecione o dispositivo BBC micro:bit e clique em Conectar.
- 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.
- Recarregue a página.
- Clique no botão Conectar.
- Na caixa de diálogo do seletor de porta serial, selecione o dispositivo BBC micro:bit e clique em Conectar.
- 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:
- Recarregue a página.
- Clique no botão Conectar.
- Na caixa de diálogo do seletor de porta serial, selecione o dispositivo BBC micro:bit e clique em Conectar.
- 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.
- Recarregue a página.
- Clique no botão Conectar.
- Na caixa de diálogo do seletor de porta serial, selecione o dispositivo BBC micro:bit e clique em Conectar.
- Você verá um sorriso na matriz de LED micro:bit.
- 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.
- Recarregue a página.
- Clique no botão Conectar.
- Na caixa de diálogo do seletor de porta serial, selecione o dispositivo BBC micro:bit e clique em Conectar.
- Você verá um sorriso na matriz de LED micro:bits.
- 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.
- Recarregue a página.
- Clique no botão Conectar.
- Na caixa de diálogo do seletor de porta serial, selecione o dispositivo BBC micro:bit e clique em Conectar.
- Você verá um sorriso na matriz de LED micro:bit.
- 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.
- Recarregue a página.
- Clique no botão Conectar.
- Na caixa de diálogo do seletor de porta serial, selecione o dispositivo BBC micro:bit e clique em Conectar.
- Você verá um sorriso na matriz de LED micro:bit.
- 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.
- Recarregue a página.
- Clique no botão Conectar.
- Na caixa de diálogo do seletor de porta serial, selecione o dispositivo BBC micro:bit e clique em Conectar.
- Você verá um sorriso na matriz de LED micro:bits.
- 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.
- Recarregue a página.
- Clique no botão Conectar.
- Na caixa de diálogo do seletor de porta serial, selecione o dispositivo micro:bit da BBC e clique em Conectar.
- Você verá um sorriso na matriz de LED micro:bit
- 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.