Pular para o conteúdo
miblo

SDK

A tela App do Miblo: cards, frames, layers e peek

Desenhe a tela App do Miblo: screen.card com cada tipo de item, frame, layer, show, peek e clear, os limites da flash e o que fazer com cada erro.

A tela App

Desde o Miblo 1.25, o rodízio de telas do aparelho (Visão geral, Limites, Sessões) tem a tela App, que o seu programa desenha. Ela pode ser:

  • um card: um título e até quatro itens (um número grande, um anel, uma barra, uma linha de texto, um mini gráfico), desenhados pelo próprio Miblo com as fontes e as cores dele;

  • um frame: uma imagem inteira de 240x240, guardada na flash do Miblo;

  • um layer: o card desenhado por cima de um frame. É o jeito certo de ter uma tela bonita: o fundo é enviado uma vez, e depois só o número muda.

Regras que valem sempre:

  • O Miblo vem primeiro. Avisos, "precisa de você", configuração e códigos sempre aparecem por cima da tela App.

  • O nome do programa está sempre na tela, para a pessoa saber de quem é. É o tool que você passa em screen().

  • Cada programa tem a sua tela. Com vários apps, a tela App mostra um por vez e passa ao próximo a cada 15 s (miblo apps rotation <segundos>; 0 mostra só o fixado).

  • Quando os apps aparecem é escolha da pessoa (miblo apps when, a página do aparelho ou o app Miblo): quando ninguém está trabalhando (padrão: depois de 60 s sem sessão de IA rodando ou esperando), sempre no rodízio, ou só quando eu pedir (fixado ou peek).

screen()

const screen = miblo.screen({ tool: 'steps' });

tool segue as regras do tool de uma sessão (1 a 20 caracteres, não um nome do Claude Code, sem _ no começo). Cada escrita leva o cabeçalho x-miblo-sdk-tool; o pacote manda por você. A tela de um programa é a do app que ele declarou (veja Apps), senão a do nome dele.

card()

await screen.card({
  v: 1,
  title: 'Passos',
  icon: 'steps',
  items: [
    { t: 'big', value: '8.432', label: 'hoje' },
    { t: 'ring', value: 0.61, label: 'meta 14k', color: 'amber' },
  ],
});

Os campos do card

CampoRegras
vSempre 1.
titleOpcional, até 24 caracteres, no alto da tela (o nome do programa vem junto).
itemsDe 1 a 4 itens, de cima para baixo.
iconOpcional, um nome do conjunto abaixo. Um nome fora dele é desenhado como nenhum.
bgOpcional, "frame:0" a "frame:3": desenha o card sobre esse frame (um layer). layer() preenche para você.

O card inteiro tem no máximo 1024 bytes. Campos desconhecidos são ignorados; um tipo de item desconhecido recusa o card.

Os itens

tCamposComo aparece
bigvalue (texto ou número inteiro, até 10 caracteres, obrigatório), label?, color?Um número grande com legenda.
ringvalue (0 a 1), label?, color?Um anel que enche, como os dos limites.
barvalue (0 a 1), label?, color?Uma barra que enche.
textvalue (até 40 caracteres, obrigatório), color?Uma linha de texto.
sparkvalues (2 a 24 números), label?, color?Um mini gráfico de linha.
rowitems: exatamente 2 dos outros (nunca um row)Dois itens lado a lado.
  • label: até 16 caracteres.

  • color: amber, blue, green, red, white ou grey. Sem cor, cada tipo usa a sua (big branco, anel coral, barra violeta, gráfico azul, texto cinza). Não há cores hexadecimais na v1.

  • icon: steps, heart, water, coffee, sun, moon, cloud, bolt, check, star, clock, calendar, chart, music, mail, code.

Como os textos são tratados

  • Espaços repetidos viram um, caracteres invisíveis, de controle e de direção (bidi) são removidos, as pontas são aparadas e cada texto é cortado no seu limite.

  • Frases do Miblo são recusadas (bad_request): um texto que comece como um aviso do Miblo (approved:, phone:, miblo:, allow?, task: e os equivalentes em português) ou que use as frases de alerta do aparelho ("NEEDS YOU", "Asked permission", "Asked a question" e traduções). A tela App nunca se passa por um aviso do Miblo.

  • O campo com problema vem em field, no formato items[1].items[0].value.

A vida de um card

O card vive na memória do aparelho, não na flash: mudar o card a cada minuto não gasta nada. Quando o Miblo reinicia, a ponte manda o card de novo em até 15 minutos. Um card sem atualização por 24 horas sai (a tela mostra "sem dados" com o nome do programa).

Devolve { ok, current, delivered, gadgets }: current diz se a sua tela é a que está na tela App agora. Cards: 2 por segundo, em rajadas de 5.

frame()

Uma imagem de 240x240 num dos 4 slots da flash do Miblo.

import fs from 'node:fs';
await screen.frame(0, fs.readFileSync('background.png'));

O que você pode mandar:

  • PNG (8 bits, RGB ou RGBA, não entrelaçado, até 4096 de lado): é ajustado a 240x240 mantendo a proporção;

  • pixels RGBA crus: { rgba, w, h } (TS, com Uint8Array) ou {"rgba": bytes, "w": ..., "h": ...} (Python);

  • um arquivo MFRM1 pronto (o formato do Miblo).

O computador converte a imagem para no máximo 64 cores, codifica por linhas e confere tudo antes de mandar; não precisa de nenhum outro programa. SVG ou HTML você mesmo renderiza (um canvas, sharp, resvg...) e manda o PNG. Um fundo com cores chapadas e texto fica entre 5 e 30 KB; uma foto costuma passar de 60000 bytes depois da redução e é recusada com o tamanho (too_many_colours).

Devolve { ok, slot, bytes, colours, delivered, gadgets }. Um upload pode levar até 60 s (o arquivo vai em pedaços para cada aparelho).

A flash se desgasta

A flash do Miblo aguenta um número limitado de gravações, e um frame é gravado nela a cada envio. Por isso:

  • cada slot aceita 1 gravação a cada 10 s e 100 por dia (rate_limited com retryAfter, conferido no computador antes de converter ou mandar qualquer coisa); a contagem sobrevive a um reinício;

  • o que muda vai no card, não no frame. Um layer redesenha o card sem gravar nada;

  • um frame danificado ou incompleto é apagado, nunca desenhado.

Os slots são compartilhados

Os 4 slots são de todos os programas. Um slot é do programa que o gravou até ele o liberar (removeFrame(), clear()) ou a tela dele sair. Gravar no slot de outro dá slot_taken, com o nome do dono em owner.

layer()

O card desenhado sobre um frame: grave o fundo uma vez e depois só mande cards.

await screen.layer(0, { v: 1, title: 'Passos', items: [{ t: 'big', value: '8.432', label: 'hoje' }] });

É o mesmo que card() com bg: "frame:0". Desenhe o fundo deixando espaço para os itens: o layout do card é fixo por número de itens.

show()

Fixa a sua tela: ela fica na tela App (sem rodízio) e o aparelho fica na tela App, como miblo mode app. show(false) solta.

await screen.show(true);

Devolve { ok, shown, delivered, gadgets }. Uma vez a cada 10 s.

peek()

"Olha agora": a tela App vem para a frente por 1 a 10 segundos (padrão 10), a não ser que o Miblo esteja dando um aviso. Para o momento que importa (o bitcoin subiu 5 %, o build quebrou).

await screen.peek(10);
  • Precisa de um card antes (no_card).

  • Uma vez a cada 10 minutos por programa, e no máximo uma a cada 10 s (rajadas de 3) somando todos (rate_limited com retryAfter).

  • Traz a sua tela para a frente primeiro, a não ser que outra esteja fixada.

Devolve { ok, seconds, delivered, gadgets }.

clear(), removeFrame() e status()

TypeScriptPythonO que faz
screen.removeFrame(slot)screen.remove_frame(slot)Apaga um slot seu: { ok, slot, delivered, gadgets }.
screen.clear()screen.clear()Tira a sua tela: o card, os seus slots e o fixado. miblo screen clear tira todas.
screen.status()screen.status()A sua tela: { card, frames: [{ slot, bytes, at }], shown, current, rotation }.

O que o aparelho faz com cada chamada

ChamadaNo computadorNo aparelho
card() / layer()Valida, limpa os textos, guardaRedesenha só o que mudou; fica na memória
frame()Confere o desgaste, converte para 64 cores, codificaGrava o slot inteiro na flash e confere antes de usar
show()Marca a sua tela como fixadaFica na tela App, sem rodízio
peek()Confere os limitesMostra a tela App por alguns segundos, se não houver aviso
clear()Esquece a sua tela e os seus slotsTira o card e apaga os seus frames

Um aparelho desligado ou ocupado é atualizado depois pela própria ponte. Um Miblo com firmware anterior à 1.25 responde unsupported em gadgets. O app Miblo e o celular desenham o mesmo card, pixel a pixel, com o mesmo código do aparelho.

Erros e o que fazer

StatuscodeQuandoO que fazer
400bad_request (+ field)Um card ou nome que as regras recusamVeja field; corte o texto ou troque a frase
400bad_frameNão é um PNG ou MFRM1 que o conversor aceita (ex.: PNG entrelaçado)Salve como PNG de 8 bits, sem entrelaçamento
409slot_taken (+ owner)O slot é de outro programaUse outro slot
409no_cardUm peek antes de qualquer cardMande um card antes
409too_many_screensA ponte já tem 64 telas de programasscreen.clear() nas que não usa, ou miblo screen clear; uma tela parada por um dia sai sozinha
413too_large (+ max)Card acima de 1024 bytes, frame acima de 1 MiB, MFRM1 acima de 60000 bytes de imagemEncurte o card; reduza a imagem
415unsupported_media_typeUm frame que não é octet-stream nem JSONUse frame() do pacote
422too_many_colours (+ bytes)A imagem é detalhada demais para um frameUse cores chapadas e menos degradê
429rate_limited (+ retryAfter)Cards, frames (o desgaste), show ou peek acima do limiteEspere retryAfter segundos; mude o card, não o frame

Exemplo completo: um app de passos

O fundo vai uma vez; depois, a cada minuto, só o número muda (nenhuma gravação na flash).

// MIBLO_TOKEN=$(miblo sdk token) node steps.mjs
import fs from 'node:fs';
import { MibloStatus } from '@miblo/status';

const screen = new MibloStatus().screen({ tool: 'steps' });
await screen.frame(0, fs.readFileSync(new URL('./steps-background.png', import.meta.url)));
await screen.show(true);                       // ou: miblo mode app

let steps = 8432;
for (;;) {
  await screen.layer(0, { v: 1, title: 'Passos', icon: 'steps', items: [
    { t: 'big', value: steps.toLocaleString('pt-BR'), label: 'hoje' },
    { t: 'ring', value: Math.min(1, steps / 14000), label: 'meta 14k', color: 'amber' },
  ] });
  await new Promise((r) => setTimeout(r, 60_000));
  steps += Math.floor(Math.random() * 120);
}

O exemplo em TypeScript e a imagem de fundo vêm dentro do pacote @miblo/status (examples/steps.mjs e examples/steps-background.png).