
Eu uso o Claude todos os dias, e a pergunta que faço com mais frequência não é sobre código. É “quanto da minha semana ainda resta?”. A tela /usage do Claude Code responde, mas só quando eu paro e digito o comando. Eu queria a resposta na minha mesa, do jeito que um relógio mostra as horas.
O M5Stack Basic é uma boa escolha. É um dispositivo pronto: uma tela de 2 polegadas 320×240, três botões, uma bateria e um ESP32 em uma carcaça de 54 mm. Sem protoboard, sem solda.
Este artigo descreve o dashboard: o que ele mostra, como os dados vão do Claude até a tela, como o dispositivo se conecta sem nenhuma configuração e os passos para construir o seu. A interface foi projetada no M5Stack Core no Velxio antes de tocar o hardware, e essa é a rota que recomendo se você quiser construir um. Você também pode executar este firmware no seu navegador, com dados de demonstração, sem o dispositivo.
O dashboard no dispositivo: assista no YouTube.
Todo o código, o firmware e a ponte no PC, está no GitHub sob a licença MIT: velxio/m5claude. A seção “O código, parte por parte” abaixo percorre o que cada arquivo faz.
O hardware

A placa é o M5Stack Basic Core IoT Development Kit v2.7:
- ESP32-D0WDQ6-V3, dois núcleos a 240 MHz.
- 520 KB de SRAM e sem PSRAM. Essa restrição molda o firmware.
- 16 MB de flash.
- Um painel IPS de 2 polegadas 320×240 com controlador ILI9342C, atrás de vidro na v2.7.
- Três botões (A, B, C) nos GPIO 39, 38 e 37.
- Um chip USB-serial CH9102, Wi-Fi e Bluetooth LE.
- Um alto-falante de 1 W no GPIO 25.
- Um chip de energia IP5306 com uma bateria de 110 mAh, legível via I2C.
Nada mais é necessário: o dashboard usa apenas o que está na carcaça. A placa não tem IMU, microfone, LEDs nem relógio de tempo real, então o dashboard usa todo o resto: a tela e os botões, o alto-falante para alertas, o backlight para economizar bateria e o chip de energia para mostrar a carga.
O que a tela mostra
O dashboard funciona com dois tipos de conta do Claude, e com várias de cada:
- Assinaturas Pro e Max. Elas têm limites de uso: uma janela de sessão de 5 horas, um limite semanal e limites semanais por modelo. A tela mostra cada um como uma porcentagem com o horário de reset.
- Organizações de API. Elas pagam por token, então a tela mostra gastos: hoje e nos últimos sete dias, em dólares.
Para ambos, mostra tokens por dia na última semana, divididos em entrada, saída, leituras de cache e escritas de cache, e por modelo.
São quatro telas. B passa para a próxima, A e C alternam entre contas, segurar B pede dados novos ao PC, e segurar C abre as configurações. Estas são todas as telas, com a animação que cada uma reproduz ao abrir:

Visão geral. Um cartão por conta. O layout se adapta à quantidade de contas: uma conta ocupa a tela, duas ganham cartões mais altos com uma linha para o uso de hoje, e três ou mais viram uma lista rolável.


Limites. O limite semanal como um anel, com a sessão e os limites por modelo ao lado. Os nomes dos modelos vêm do próprio Claude, então as linhas acompanham os limites que o seu plano tiver. As cores mudam em 60% (âmbar) e 85% (vermelho).

Tokens. Hoje, os últimos sete dias e o valor em dólares, depois tokens por dia com uma linha tracejada na média. Para uma assinatura, o valor em dólares é quanto os mesmos tokens custariam pelos preços de tabela da API. Não é uma cobrança, mas é uma forma útil de ver com que intensidade você usa o plano.


Detalhamento. Os tokens da semana por tipo e por modelo. Com o Claude Code, as leituras de cache costumam ser a maior parte do total.


O ponto ao lado do relógio mostra a conexão: verde enquanto os dados estão atualizados, âmbar quando estão defasados, vermelho quando nenhum PC está conectado. As fotos mostram a minha própria conta. As capturas de tela usam contas de demonstração.
Bateria, som e configurações
Ao lado do relógio está a bateria, desenhada como os celulares desenham: o nível, e um raio com um preenchimento que sobe enquanto carrega. O chip de energia IP5306 reporta apenas quatro degraus (25, 50, 75 e 100%), então o número muda em passos de 25.

O alto-falante é usado para alertas. Quando uma sessão ou um limite semanal passa de 90% (ou 80%, se você escolher), o dispositivo toca um bipe duplo curto e mostra qual conta e qual limite. Quando um limite é resetado, toca um bipe ascendente. Pedidos de aprovação de um computador novo também emitem som. Os cliques dos botões são opcionais.
Segurar C abre as configurações. A e C navegam, B altera um valor, e segurar B fecha a tela. As configurações são salvas no dispositivo.

- Idioma. Inglês ou espanhol. Todas as strings da tela ficam em uma única tabela, então adicionar um idioma é só mais uma entrada.
- Brilho. 25, 50, 75 ou 100%.
- Sons. Desligado, apenas alertas, ou alertas e cliques dos botões.
- Alerta em. 80%, 90% ou desligado.
- Tela desligada na bateria. Nunca, após um minuto ou após cinco. Qualquer botão a acorda, e o toque que a acorda não faz mais nada. Com uma bateria de 110 mAh, isso importa.
- Escurecer à noite. Reduz a luz de fundo das 22h às 7h, usando o relógio do PC.
- Computadores pareados. Quantos estão aprovados, e B os esquece após uma confirmação.


Fazendo 110 mAh durarem
O primeiro firmware drenava a bateria em bem menos de uma hora. A CPU rodava a 240 MHz, a tela se repintava a cada segundo, e o rádio transmitia na potência máxima com um intervalo de conexão de 15–30 ms. Nada disso é necessário para mostrar um número que muda uma vez por minuto. Na bateria, o firmware agora faz o seguinte:
- CPU a 80 MHz, a velocidade mais baixa que o Bluetooth suporta. Volta a 240 MHz na alimentação USB.
- Luz de fundo: escurece após 20 segundos sem toque em botão, e desliga após o tempo escolhido nas configurações.
- Standby: 15 segundos após a tela desligar, o rádio desliga e o ESP32 entra em light sleep. A RAM é mantida, então qualquer botão traz a tela de volta na hora com os últimos dados, e a ponte reconecta em segundos. A cada 10 minutos um timer o acorda por até 45 segundos com a tela ainda desligada. Ele busca dados novos, e se um limite cruzou o limite de alerta, acende a tela e toca o alerta.
- Rádio: 0 dBm em vez de +9, advertising a cada 0,5–1 s, e um intervalo de conexão de 100–200 ms com slave latency. Um snapshot de 600 bytes por minuto precisa de pouco mais.
- Repinturas acontecem apenas quando o minuto muda ou chegam dados novos, e não há animações ociosas na bateria.
Para uso o dia todo longe da mesa, o Battery Module 13.2 se encaixa embaixo do Basic e adiciona 1500 mAh.
Como funciona
A regra de projeto é que o dispositivo não sabe nada. Ele não guarda credenciais do Claude nem configurações de Wi-Fi. Um pequeno programa no PC, a ponte, coleta tudo e envia ao dispositivo um snapshot para desenhar. Isso mantém os segredos fora de um aparelho que fica em cima da mesa, e significa que trocar de computador ou de rede não exige nenhuma reconfiguração.
De onde vêm os dados
A ponte tem cerca de 1.100 linhas de Python com duas dependências (pyserial e bleak). Ela lê três fontes:
- Limites do plano, a partir do login do Claude Code. Cada diretório de configuração do Claude Code (
~/.claude,~/.claude-work, …) guarda o login de uma conta. A ponte encontra todos e usa cada token para ler os mesmos limites que a tela/usagedo Claude Code mostra. Ela apenas lê o token e nunca o renova, porque renovar rotacionaria o token e desconectaria o Claude Code. - Tokens, a partir dos transcripts do Claude Code. Toda resposta que o Claude Code recebe é gravada em um arquivo JSONL em
projects/, com o uso de tokens e o modelo. A ponte soma esses valores por dia, remove duplicatas (uma sessão retomada copia mensagens anteriores para um novo arquivo) e os precifica com a tabela de preços da API. - Gastos das organizações de API, a partir da Usage and Cost Admin API.
usage_report/messagesfornece tokens por dia e por modelo.cost_reportfornece o gasto real. Ambos exigem uma chave da Admin API (sk-ant-admin01-...).
Duas lições de rodar isso contra contas reais:
- O endpoint de limites tem limite de requisições. Enquanto eu testava, ele começou a retornar HTTP 429 sem
Retry-After. Agora a ponte lê os limites a cada cinco minutos em vez de a cada minuto, mantém a última resposta boa em disco e continua exibindo-a durante erros temporários. Os tokens ainda atualizam a cada minuto, porque vêm de arquivos locais. - Os transcripts ficam grandes. Os meus tinham 665 MB. A ponte lê apenas arquivos alterados nos últimos oito dias e reprocessa um arquivo somente quando seu tamanho ou horário de modificação muda. Uma atualização leva cerca de um segundo.
O snapshot
Cada atualização produz uma linha JSON de cerca de 600 bytes:
{"v":1,"ts":1792770195,"tz":3600,"acc":[
{"n":"Personal","p":"Max 20x","k":"sub","ok":1,
"s":[42,1792778235],"w":[67,1793037195],"x":[["Opus",54,1793037195],["Sonnet",31,1793037195]],
"td":2840000,"tk":[2310000,1480000,15900000,1120000],"c":84.2,"ct":11.35,
"d":[1900000,3400000,2600000,4100000,3300000,2670000,2840000],"m":[["Opus",61],["Sonnet",34],["Haiku",5]]}]}
s, w e x são os limites de sessão, semanal e por modelo como [percentual, horário de reset]. d é tokens por dia. ts e tz são o relógio do PC e o deslocamento UTC. O M5Stack Basic não tem relógio de tempo real nem banco de dados de fusos horários, então o dispositivo pega ambos do snapshot e conta os segundos a partir dali. Isso basta para “reinicia em 2h 14m” e “Ter 04:00”.
Conectando sem configuração
Eu não queria nenhuma configuração. Digitar uma senha de Wi-Fi em um dispositivo com três botões é desagradável. Salvar o endereço IP do PC no dispositivo quebra assim que você troca de computador. Então o dispositivo suporta dois links, e nenhum deles precisa de ajustes.
USB. Conecte o cabo. A ponte procura portas seriais cujo chip USB seja de um M5Stack (CH9102 ou CP2104). Ela abre a porta sem alternar DTR e RTS, para que o ESP32 não reinicie, e espera por uma saudação. O firmware envia {"hello":"m5claude",...} a cada dois segundos até os dados chegarem. Se nenhuma saudação chegar em alguns segundos, a ponte libera a porta e a deixa em paz por um tempo, para que outras placas ESP32 e ferramentas de gravação não sejam afetadas.
Bluetooth LE. O firmware anuncia um serviço GATT com duas características: uma na qual o PC escreve linhas, e outra na qual o dispositivo envia notificações. A ponte procura pelo UUID do serviço e se conecta. Não há diálogo de pareamento do Windows, e nada para escolher de uma lista.
Como qualquer computador ao alcance poderia encontrar o dispositivo, cada novo PC é aprovado uma vez, no próprio dispositivo:

A ponte mantém um id aleatório em ~/.m5claude/pc_id. O dispositivo guarda os ids de até quatro PCs aprovados na NVS. Um PC conhecido é aceito na hora, e segurar A ao ligar esquece todos eles. Um dispositivo que já está conectado por USB é ignorado no Bluetooth, para não pedir a aprovação do mesmo PC duas vezes.
Até a ponte estar rodando, o dispositivo mostra o que fazer:

O firmware
O firmware é um sketch Arduino compilado com arduino-cli no core arduino-esp32 2.0. Ele usa três bibliotecas:
- LovyanGFX para desenhar a tela.
- NimBLE-Arduino para Bluetooth. O NimBLE usa cerca de metade da RAM da pilha Bluedroid padrão.
- ArduinoJson 7 para interpretar os snapshots.
O código da UI não depende do hardware. Ele desenha em um alvo LovyanGFX e lê os três botões. Tudo o que é específico da placa (o painel, USB, BLE, NVS) fica no sketch.
Alguns detalhes que fazem parecer um produto em vez de uma demo:
- Texto com anti-aliasing. As fontes são Inter, convertidas para o formato VLW suave do LovyanGFX por um pequeno script Python em seis tamanhos. O script inclui apenas os caracteres que cada tamanho usa, cerca de 77 KB no total.
- Medidores com anti-aliasing. Os anéis são desenhados pixel a pixel: a cobertura é calculada a partir da distância até as bordas do anel, e as pontas são arredondadas. Barras e cartões usam os retângulos arredondados suaves do LovyanGFX.
- Animações curtas. A cada troca de tela, barras e números crescem até seus valores ao longo de meio segundo com uma curva de ease-out.
Desenhando sem PSRAM, e os riscos diagonais
Um quadro completo de 320×240 em cores de 16 bits tem 150 KB. O maior bloco de memória livre do ESP32, com o Bluetooth rodando, é menor que isso. Então a UI é desenhada quatro vezes por quadro em uma faixa de 320×60 (38 KB). Cada passada desenha tudo deslocado pelo offset da faixa, e as partes que caem fora da faixa são recortadas.
A primeira vez que isso rodou na placa, a tela mostrou riscos diagonais, e os botões do prompt de aprovação apareceram duas vezes, no topo e na parte de baixo. A configuração do painel foi meu primeiro suspeito. Reescrevi à mão a partir da documentação da M5Stack, e nada mudou. A causa estava em outro lugar:
O LovyanGFX envia um sprite para o painel com DMA, em segundo plano. Meu loop começava a desenhar a próxima faixa no mesmo buffer enquanto a anterior ainda estava sendo enviada, então o painel recebia metade de uma faixa e metade da seguinte. A correção usa dois buffers alternadamente e espera a transferência anterior antes de enviar:
for (int y = 0, k = 0; y < 240; y += 60, k ^= 1) {
LGFX_Sprite& band = bands[k];
band.fillScreen(BG);
draw_ui(band, /*offset_y=*/y); // runs while the other buffer is still being sent
lcd.waitDMA(); // the other buffer's transfer has finished
band.pushSprite(&lcd, 0, y); // starts this buffer's transfer
}
lcd.waitDMA();
Custa um buffer de 38 KB a mais, de cerca de 275 KB de RAM livre.
O código, parte por parte
Baixe o código com git clone https://github.com/velxio/m5claude.git, ou como ZIP pelo botão Code do repositório. Ele tem duas metades que não compartilham nada além do protocolo: bridge/ roda no PC, e firmware/ mais src/ rodam no M5Stack.
No M5Stack
firmware/m5claude/m5claude.ino: tudo o que é específico da placa.
- A configuração do painel: ILI9342C no VSPI, seus pinos, rotação e inversão de cores.
- O link USB. Ele lê a porta serial e escreve as respostas.
- O link Bluetooth. É um serviço GATT NimBLE com uma característica na qual o PC escreve e outra sobre a qual o dispositivo notifica. Os bytes recebidos nos callbacks do NimBLE vão para um buffer de stream e são processados em
loop(), então o restante do código nunca roda na tarefa do Bluetooth. - A lista de PCs aprovados e as configurações, guardadas na NVS com
Preferences. - A bateria: a cada dois segundos ele lê os bits de nível, carregando e carga completa do IP5306 via I2C.
- O alto-falante: um pequeno sequenciador toca os tons de alerta, reset e pareamento com ondas quadradas de LEDC em um duty cycle baixo, e depois mantém o pino em nível baixo para o alto-falante não chiar entre os sons.
src/app/app.cpp: o protocolo, independente do link.
- Cada link (USB ou Bluetooth) tem seu próprio buffer de linha e estado. Ele faz o parse de uma linha a cada newline, e repete a saudação a cada dois segundos até os dados chegarem.
- Um link não confiável passa por aprovação: o dispositivo pergunta à UI, memoriza a resposta e responde
pair: okoudenied. - Ele transforma um toque longo no B em
{"req":"refresh"}. - Ele guarda uma cópia do snapshot anterior, para a UI saber quando um limite cruzou o limiar de alerta ou foi resetado.
- Ele mantém o relógio: o horário do último snapshot mais os segundos desde que ele chegou.
src/model/: os dados.
model.hdescreve uma conta: plano, limites em porcentagem e horário de reset, tokens por dia, custo e modelos.parse.cpppreenche o modelo a partir de um snapshot com ArduinoJson. Ele faz o parse em uma cópia separada e substitui os dados atuais apenas se a linha inteira for válida, então uma linha corrompida nunca deixa a tela meio atualizada.mock.cpptem as contas de demonstração usadas nas capturas de tela deste artigo, úteis durante o design.
src/ui/: as telas.
ui.cppdesenha as quatro views, as configurações e o prompt de aprovação. Ele trata os botões: um clique, ou um toque longo após 650 ms. Ele roda as animações de meio segundo, decide o nível do backlight (brilho escolhido, escurecimento noturno, desligado quando ocioso na bateria), dispara os alertas de limite e renderiza em faixas com dois buffers de DMA.i18n.hguarda todas as strings da tela, em inglês e espanhol, incluindo os nomes dos dias da semana e as mensagens de erro da ponte.gfx.hguarda as cores e os auxiliares de desenho: barras arredondadas, o anel com anti-aliasing e o offset que permite ao mesmo código de desenho preencher qualquer faixa.fonts_gen.hcontém as fontes Inter. Elas são geradas portools/make_fonts.py.
No PC
A ponte fica em bridge/m5claude_bridge/. Suas únicas dependências são pyserial e bleak. As chamadas HTTPS usam a biblioteca padrão.
__main__.py: a linha de comando e o loop principal.
- Os comandos são
run,snapshot,accounts,init,add-apieautostart. - Antes de rodar, ele adquire um lock (um socket em escuta no localhost), então apenas uma ponte roda por vez.
runcoleta um snapshot a cada 60 segundos, ou imediatamente quando um dispositivo pede um, e o entrega ao hub.
autostart.py registra a ponte para iniciar no login sem janela: a chave de registro Run com pythonw no Windows, um LaunchAgent no macOS, uma unit de usuário do systemd no Linux.
config.py: as contas.
- Ele detecta todo diretório de configuração do Claude Code com um login.
- Como alternativa, ele lê
~/.m5claude/config.json, onde você pode nomear contas e adicionar organizações de API.
sources/: um arquivo por fonte de dados.
subscription.pylê o login do Claude Code de uma conta e seus limites de plano.local_logs.pylê os transcripts do Claude Code. Ele faz o parse de forma incremental, remove mensagens duplicadas e totaliza tokens por dia e por modelo.admin_api.pylê a Usage and Cost Admin API para organizações de API, seguindo a paginação.
pricing.py guarda os preços de tabela da API por família de modelo. Ele estima o valor do uso da assinatura.
snapshot.py: monta o snapshot.
- Ele coleta cada conta em paralelo, então uma conta lenta não atrasa as demais.
- Ele aplica a política de limites: a cada cinco minutos, com a última resposta boa em cache no disco e mantida durante erros temporários.
hub.py guarda o snapshot mais recente e o entrega a cada dispositivo conectado. Um link lento recebe apenas o mais recente, nunca um acúmulo.
transports/: um arquivo por link.
usb_serial.pyencontra os chips USB do M5Stack e mantém uma porta apenas se o firmware a cumprimentar.ble.pyescaneia, conecta, espera a aprovação e escreve em blocos que cabem no MTU negociado.tcp.pyé um link local para desenvolvimento.
O README do repositório tem o mesmo tour em tabelas, além de solução de problemas.
Monte o seu
A ordem que eu recomendo: desenhe as telas no Velxio, depois grave o firmware na placa, depois conecte a ponte.
1. Execute e altere no M5Stack Core no Velxio
O Velxio simula o M5Stack Core com sua tela de 320×240, os botões A, B e C e o chip da bateria. Montar o layout de uma tela de 2 polegadas envolve muitos ajustes pequenos (um rótulo dois pixels abaixo demais, um número que não cabe no seu cartão), e cada um leva segundos no navegador em vez de um ciclo de gravação.
O dashboard está na galeria de exemplos do Velxio, com os mesmos arquivos-fonte do repositório:
- Abra M5Stack Core: Claude usage dashboard (m5claude) e clique no botão de play. Não é preciso cadastro. Ele começa com as contas de demonstração deste artigo.
- Clique em A e C para escolher uma conta e em B para percorrer as visualizações. O simulador executa o firmware mais devagar que a placa, então para abrir as configurações, segure C por alguns segundos.
- Para ver seus próprios números, execute
m5claude-bridge snapshotno seu PC e cole a linha que ele imprime no monitor serial. O primeiro comentário do sketch tem uma linha de exemplo que você pode editar. - Altere as telas em
ui.cppe execute de novo. Quando estiverem boas, grave na placa.
Duas coisas diferem da placa: o Velxio não tem Bluetooth, então esse link fica de fora e o monitor serial é o único link, e o código do alto-falante usa as chamadas do core ESP32 Arduino mais recente. Cada alteração está marcada no sketch.
Para começar um dashboard seu a partir de uma tela vazia, M5Stack Core: Hello LCD + Buttons é o menor exemplo. Ele usa o M5GFX da M5Stack, que é construído sobre o LovyanGFX e compartilha sua API de desenho, então o código transita entre os dois.
2. Grave o firmware
git clone https://github.com/velxio/m5claude.git && cd m5claude
arduino-cli core install esp32:esp32
arduino-cli lib install LovyanGFX NimBLE-Arduino ArduinoJson
./firmware/build.sh /dev/ttyUSB0 # Linux / macOS
.\firmware\build.ps1 -Upload # Windows, finds the COM port by itself
Os scripts compilam para o M5Stack Core com um esquema de partições que deixa espaço para um app grande (PartitionScheme=no_ota). O firmware tem cerca de 820 KB. Duas configurações me pegaram de surpresa:
- C++17. O core arduino-esp32 2.0 compila como C++11 por padrão, e o código usa recursos de C++14 e C++17. Adicione
--build-property "compiler.cpp.extra_flags=-std=gnu++17". - O painel. Eu configuro o ILI9342C explicitamente (VSPI, MOSI 23, MISO 19, SCK 18, CS 14, DC 27, RST 33, backlight 32,
offset_rotation = 3) em vez de usar a autodetecção de placa do LovyanGFX. O painel IPS da v2.7 precisa de cores invertidas. Ele é detectado da mesma forma que as próprias bibliotecas da M5Stack fazem: pela forma como a linha de reset é lida de volta.
3. Execute a ponte
pip install -e bridge # from the repository root; installs pyserial and bleak
m5claude-bridge accounts # which accounts it found
m5claude-bridge autostart on # start now, and at every login
autostart on registra a ponte para iniciar quando você faz login, sem janela. No Windows ele adiciona um valor sob a chave de registro Run do usuário que inicia o pythonw. No macOS ele instala um LaunchAgent, e no Linux um serviço de usuário do systemd. autostart status mostra se ela está em execução e as últimas linhas do seu log, em ~/.m5claude/bridge.log. Apenas uma ponte roda por vez: uma segunda competiria pela porta USB e pela conexão Bluetooth, então ela encerra com uma mensagem. Executar m5claude-bridge sozinho a mantém em primeiro plano, com o log no terminal.
Contas Pro e Max são encontradas a partir dos diretórios de configuração do Claude Code. Para adicionar uma segunda assinatura, faça login dela em seu próprio diretório:
$env:CLAUDE_CONFIG_DIR = "$HOME\.claude-work"; claude # then /login
Organizações de API precisam de uma chave de Admin API, do Claude Console em Settings → Admin keys:
m5claude-bridge add-api "Acme API" --key-env ACME_ADMIN_KEY
4. Conecte
Conecte o cabo USB, ou deixe o dispositivo na mesa e deixe o Bluetooth encontrá-lo. Na primeira vez via Bluetooth, pressione C no dispositivo para aprovar o PC. Os dados aparecem em alguns segundos.
Ressalvas
- O endpoint de limites do plano não é uma API pública. É o endpoint que a tela
/usagedo Claude Code usa, e ele pode mudar. A ponte recorre a nomes de campos mais antigos, mas uma mudança de formato exigiria uma atualização. - Os logins expiram. O Claude Code renova seu token quando você o usa. Uma conta que você não usa há algumas horas mostra “Login expired” até que o Claude Code rode com ela de novo.
- O valor em dólares para assinaturas é uma estimativa pelos preços de tabela da API. Organizações de API mostram seu gasto real a partir do relatório de custos.
Próximos passos
- Um ícone na bandeja do PC mostrando o estado do link.
- Um histórico no cartão microSD, para gráficos maiores que sete dias.
- Um LED RGB na porta Grove que fica vermelho acima de 90%, visível do outro lado da sala.
- Um link Wi-Fi opcional, configurado a partir da ponte via USB ou Bluetooth, para que o dispositivo possa ficar em outra sala.
O código está em github.com/velxio/m5claude. Issues e pull requests são bem-vindos.
Agradecimentos à M5Stack por uma placa pronta para usar logo de cara, e aos autores do LovyanGFX, NimBLE-Arduino, ArduinoJson e bleak.
Os links para a loja da M5Stack nesta página são links de afiliado: se você comprar por eles, o Velxio ganha uma pequena comissão sem custo extra para você, e isso ajuda a manter o simulador gratuito.