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
toolque você passa emscreen().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>;0mostra 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' });screen = miblo.screen("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' },
],
});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
| Campo | Regras |
|---|---|
v | Sempre 1. |
title | Opcional, até 24 caracteres, no alto da tela (o nome do programa vem junto). |
items | De 1 a 4 itens, de cima para baixo. |
icon | Opcional, um nome do conjunto abaixo. Um nome fora dele é desenhado como nenhum. |
bg | Opcional, "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
t | Campos | Como aparece |
|---|---|---|
big | value (texto ou número inteiro, até 10 caracteres, obrigatório), label?, color? | Um número grande com legenda. |
ring | value (0 a 1), label?, color? | Um anel que enche, como os dos limites. |
bar | value (0 a 1), label?, color? | Uma barra que enche. |
text | value (até 40 caracteres, obrigatório), color? | Uma linha de texto. |
spark | values (2 a 24 números), label?, color? | Um mini gráfico de linha. |
row | items: exatamente 2 dos outros (nunca um row) | Dois itens lado a lado. |
label: até 16 caracteres.color:amber,blue,green,red,whiteougrey. 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 formatoitems[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'));with open("background.png", "rb") as f:
screen.frame(0, f.read())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, comUint8Array) 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_limitedcomretryAfter, 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' }] });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);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);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_limitedcomretryAfter).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()
| TypeScript | Python | O 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
| Chamada | No computador | No aparelho |
|---|---|---|
card() / layer() | Valida, limpa os textos, guarda | Redesenha só o que mudou; fica na memória |
frame() | Confere o desgaste, converte para 64 cores, codifica | Grava o slot inteiro na flash e confere antes de usar |
show() | Marca a sua tela como fixada | Fica na tela App, sem rodízio |
peek() | Confere os limites | Mostra a tela App por alguns segundos, se não houver aviso |
clear() | Esquece a sua tela e os seus slots | Tira 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
| Status | code | Quando | O que fazer |
|---|---|---|---|
| 400 | bad_request (+ field) | Um card ou nome que as regras recusam | Veja field; corte o texto ou troque a frase |
| 400 | bad_frame | Não é um PNG ou MFRM1 que o conversor aceita (ex.: PNG entrelaçado) | Salve como PNG de 8 bits, sem entrelaçamento |
| 409 | slot_taken (+ owner) | O slot é de outro programa | Use outro slot |
| 409 | no_card | Um peek antes de qualquer card | Mande um card antes |
| 409 | too_many_screens | A ponte já tem 64 telas de programas | screen.clear() nas que não usa, ou miblo screen clear; uma tela parada por um dia sai sozinha |
| 413 | too_large (+ max) | Card acima de 1024 bytes, frame acima de 1 MiB, MFRM1 acima de 60000 bytes de imagem | Encurte o card; reduza a imagem |
| 415 | unsupported_media_type | Um frame que não é octet-stream nem JSON | Use frame() do pacote |
| 422 | too_many_colours (+ bytes) | A imagem é detalhada demais para um frame | Use cores chapadas e menos degradê |
| 429 | rate_limited (+ retryAfter) | Cards, frames (o desgaste), show ou peek acima do limite | Espere 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);
}# MIBLO_TOKEN=$(miblo sdk token) python3 steps.py
import os, random, time
from miblo_status import MibloStatus
screen = MibloStatus().screen("steps")
with open(os.path.join(os.path.dirname(__file__), "steps-background.png"), "rb") as f:
screen.frame(0, f.read())
screen.show(True) # ou: miblo mode app
steps = 8432
while True:
screen.layer(0, {"v": 1, "title": "Passos", "icon": "steps", "items": [
{"t": "big", "value": f"{steps:,}".replace(",", "."), "label": "hoje"},
{"t": "ring", "value": min(1, steps / 14000), "label": "meta 14k", "color": "amber"},
]})
time.sleep(60)
steps += random.randint(0, 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).