Conteúdo detalhado
🎯 O robô assistente: escopo e regras
Projeto bom é projeto com escopo pequeno e bem medido. Defina por escrito, antes de começar: quais pedidos o robô atende (por exemplo, "traga o cubo", "traga a bola", "pare"), onde (mesa e sala), e o que conta como sucesso (objeto entregue na mão em até um minuto). Tudo fora disso o robô responde "não sei fazer isso".
Como ler: a linha de cima pensa; a do meio age. O validador (ciano) é a porta entre os dois: nada do modelo de linguagem chega ao motor sem passar por ele. A faixa vermelha não tem seta porque não depende de nenhuma caixa: é um botão que desliga a energia dos motores mesmo se o computador travar.
✓ Escopo que fecha
- ✓Versão mínima: só o braço na mesa, 2 objetos, comando por voz.
- ✓Versão completa: + base que leva o objeto até a sala.
- ✓Lista escrita de pedidos aceitos e de sucesso.
✗ Escopo que não fecha
- ✗"Um assistente que faz qualquer coisa na casa".
- ✗Modelo de linguagem mandando ângulos de servo.
- ✗Começar pela base antes de o braço funcionar sozinho.
Por que aprender
Integrar é onde os projetos morrem: cada parte funciona sozinha e o conjunto não. Escopo curto, arquitetura em camadas e uma trava fora do software são o que deixa você chegar ao fim com algo que funciona na frente de outras pessoas.
Conceitos-chave
🗣️ Voz → plano (LLM) → ações
O coração do projeto é um programa curto, o orquestrador. Ele recebe o texto que o Whisper ouviu (5.2), pede ao modelo local um plano em JSON usando só a lista fechada de ações (5.1), confere o plano, pede sua confirmação e executa passo a passo. Se qualquer passo falha, ele manda parar. O modo --simular troca o modelo e o hardware por versões de mentira, para você testar a lógica e as travas no computador.
🌱 Novo aqui?
Um orquestrador é o programa que não faz o trabalho pesado, mas chama cada parte na ordem certa e decide o que fazer quando algo dá errado. Lista fechada quer dizer que o modelo só pode escolher entre ações que você escreveu; qualquer ação inventada é recusada antes de chegar ao robô.
Objetivo: rodar o orquestrador completo em modo simulado e ver as três saídas possíveis: plano executado, plano recusado e parada. Python 3 puro; o modo real usa ainda o LeRobot (5.4), o pyserial (3.5) e o Ollama (5.1).
# Robô assistente: texto (da voz) -> plano em JSON (LLM) -> validação -> execução.
# Modo --simular: sem LLM e sem hardware, para testar a lógica e as travas.
import argparse, json, subprocess, sys, time, urllib.request
OBJETOS = {"cubo": "outputs/train/act-cubo/checkpoints/last/pretrained_model",
"bola": "outputs/train/act-bola/checkpoints/last/pretrained_model"}
SOLTAR = "outputs/train/act-soltar/checkpoints/last/pretrained_model"
LUGARES = ["mesa", "sala", "base"]
ACOES = { # lista fechada: nome -> argumentos permitidos
"ir_para": {"lugar": LUGARES},
"pegar": {"objeto": list(OBJETOS)},
"entregar": {},
"falar": {"texto": None},
"parar": {},
}
MAX_PASSOS = 6
PROMPT = ("Você controla um robô com braço e base móvel. Responda SÓ com JSON no formato "
'{"passos": [{"acao": "...", ...}]}. Ações permitidas: ' + json.dumps(ACOES, ensure_ascii=False) +
". Se o pedido não couber nessas ações, responda com uma única ação falar explicando.")
def planejar_simulado(texto):
t = texto.lower()
if "pare" in t or "parar" in t:
return {"passos": [{"acao": "parar"}]}
for obj in OBJETOS:
if obj in t:
return {"passos": [{"acao": "ir_para", "lugar": "mesa"}, {"acao": "pegar", "objeto": obj},
{"acao": "ir_para", "lugar": "sala"}, {"acao": "entregar"},
{"acao": "falar", "texto": f"Pronto, trouxe: {obj}."}]}
if "janela" in t: # simula um LLM que inventa uma ação fora da lista
return {"passos": [{"acao": "abrir_janela"}]}
return {"passos": [{"acao": "falar", "texto": "Não sei fazer isso."}]}
def planejar_ollama(texto, modelo):
corpo = json.dumps({"model": modelo, "stream": False, "format": "json", "options": {"temperature": 0},
"messages": [{"role": "system", "content": PROMPT}, {"role": "user", "content": texto}]})
req = urllib.request.Request("http://localhost:11434/api/chat", corpo.encode(), {"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=60) as r:
return json.loads(json.loads(r.read())["message"]["content"])
def validar(plano):
passos = plano.get("passos") if isinstance(plano, dict) else None
if not isinstance(passos, list) or not 1 <= len(passos) <= MAX_PASSOS:
return None, "plano vazio ou longo demais"
for p in passos:
regra = ACOES.get(p.get("acao"))
if regra is None:
return None, f"ação fora da lista: {p.get('acao')}"
for arg, permitidos in regra.items():
if arg not in p:
return None, f"falta '{arg}' em {p['acao']}"
if permitidos is not None and p[arg] not in permitidos:
return None, f"valor não permitido: {arg}={p[arg]}"
return passos, "ok"
def executar(passo, simular, porta_base):
a = passo["acao"]
if simular:
print(" [SIM]", json.dumps(passo, ensure_ascii=False)); time.sleep(0.2); return True
if a in ("pegar", "entregar"): # braço: políticas treinadas no 5.4
politica = OBJETOS[passo["objeto"]] if a == "pegar" else SOLTAR
tarefa = f"Pegar o {passo['objeto']}." if a == "pegar" else "Soltar o objeto na mão da pessoa."
cmd = ["lerobot-rollout", "--strategy.type=base", f"--policy.path={politica}",
"--robot.type=so101_follower", "--robot.port=/dev/ttyACM0", "--robot.id=seguidor",
f"--task={tarefa}", "--duration=20"]
return subprocess.run(cmd).returncode == 0
if a in ("ir_para", "parar"): # base móvel: ESP32 da Trilha 3 por serial
import serial
with serial.Serial(porta_base, 115200, timeout=30) as s:
s.write((f"{a.upper()} {passo.get('lugar', '')}".strip() + "\n").encode())
return s.readline().decode().strip() == "OK"
if a == "falar":
print(" ROBÔ:", passo["texto"]); return True # troque pelo Piper do 5.2
return False
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--texto", required=True, help="pedido (vem do Whisper no 5.2)")
ap.add_argument("--simular", action="store_true")
ap.add_argument("--modelo", default="<seu_modelo_ollama>")
ap.add_argument("--porta-base", default="/dev/ttyUSB0")
args = ap.parse_args()
print("PEDIDO:", args.texto)
plano = planejar_simulado(args.texto) if args.simular else planejar_ollama(args.texto, args.modelo)
passos, motivo = validar(plano)
if passos is None:
print("RECUSADO:", motivo); sys.exit(2)
print(f"PLANO ({len(passos)} passos):", [p["acao"] for p in passos])
if not args.simular and input("Confirmar execução? (s/n) ").strip().lower() != "s":
print("CANCELADO pelo operador"); sys.exit(0)
for i, p in enumerate(passos, 1):
print(f"passo {i}/{len(passos)}: {p['acao']}")
if not executar(p, args.simular, args.porta_base):
print("FALHOU no passo", i, "- parando"); executar({"acao": "parar"}, args.simular, args.porta_base); sys.exit(1)
print("CONCLUÍDO")
if __name__ == "__main__":
main()
Como verificar
- ✓python3 assistente.py --simular --texto "traga o cubo para a sala" mostra PLANO (5 passos), cinco linhas [SIM] e CONCLUÍDO.
- ✓Com --texto "abra a janela" sai RECUSADO: ação fora da lista: abrir_janela e nada é executado.
- ✓Com --texto "pare agora" o plano tem um passo só: parar.
- ✗No modo real, troque <seu_modelo_ollama> pelo modelo que você baixou no 5.1 e teste primeiro só com falar, sem braço ligado.
Por que aprender
É o padrão que a indústria usa para pôr modelos de linguagem em máquinas físicas: o modelo sugere, o código confere, a pessoa confirma, o hardware tem limite próprio. Com ele, um modelo que inventa ação vira uma linha de "RECUSADO", não um braço batendo na janela.
Conceitos-chave
👁️ Ver e pegar um objeto (visão + braço)
Pegar tem três momentos: conferir antes (o objeto está mesmo na mesa?), pegar (a política do 5.4 faz o movimento) e conferir depois (o objeto saiu da mesa?). As conferências usam a câmera: o detector YOLO26 da Trilha 3 ou a pergunta "o que você vê?" ao VLM do 5.1. Sem elas, o robô "entrega" a mão vazia e diz que deu certo.
Como ler: os losangos ciano são perguntas à câmera; o retângulo verde é o único passo em que o braço se move. As setas vermelhas são os caminhos de falha: cada um tem fim definido (avisar e parar, ou uma nova tentativa só), nunca um laço infinito.
Passo a passo para preparar o braço
Uma política por tarefa (caminho ACT)
Grave e treine "pegar o cubo", "pegar a bola" e "soltar na mão" como no 5.4. São os três caminhos de OBJETOS e SOLTAR no orquestrador.
Ou uma política com texto (caminho SmolVLA)
Com GPU suficiente, um SmolVLA ajustado recebe o pedido em --task; os três caminhos do dicionário passam a ser o mesmo.
Prove no gêmeo e no real devagar
Teste o alcance e a posição da câmera no gêmeo (5.5) e rode 20 tentativas de cada política no real antes de ligar ao orquestrador.
💡 Entregar na mão exige cuidado extra
Soltar na mão de uma pessoa é a parte mais delicada: o braço se move perto de dedos. Na apresentação, prefira "soltar numa bandeja ao lado da pessoa". Se for na mão, use só objetos leves e macios e deixe a pessoa avisada de onde o braço vai parar.
Por que aprender
Conferir com a câmera transforma um robô que "acha" que fez em um robô que sabe se fez. Esse hábito de verificar o resultado da própria ação é a malha fechada do 1.1, agora com visão e IA.
Conceitos-chave
🚗 Base móvel + braço: integração
Na versão completa, o braço vai em cima do rover das trilhas anteriores. O computador (Pi 5, Jetson ou notebook) roda o orquestrador e fala com o braço pela USB e com o ESP32 da base pela serial, com o mesmo protocolo de linhas de texto do módulo 3.5. Se você fez a Trilha 4, pode trocar "ir_para sala" por um ponto do Nav2; o orquestrador não muda.
Como ler: as linhas ciano são dados (USB e serial), as cinza e verdes são energia e comando de motor. A caixa vermelha fica no meio de propósito: um único botão corta a fonte do braço e a dos motores da base. Baterias separadas evitam que o pico do motor reinicie a placa dos servos.
Objetivo: o lado da base que responde ao orquestrador: recebe IR_PARA <lugar> ou PARAR, responde OK ou ERRO, e para sozinha se a viagem passar do tempo. A viagem aqui é simulada; troque chegou() e pararMotores() pelo código do seu rover.
// Base do robô assistente (ESP32, pacote esp32 3.x): protocolo de linhas com o orquestrador.
const unsigned long LIMITE_MS = 15000; // nenhuma viagem passa disso: para sozinha
void pararMotores() { /* chame aqui a parada do seu rover (velocidade 0 nas duas rodas) */ }
void andarRumo(const String &lugar) { /* rota fixa, seguidor de linha ou ponto do Nav2 */ }
bool chegou(unsigned long t0) { return millis() - t0 > 2000; } // simulação: 2 s de viagem
bool irPara(const String &lugar) {
if (lugar != "mesa" && lugar != "sala" && lugar != "base") return false;
unsigned long t0 = millis();
andarRumo(lugar);
while (!chegou(t0)) {
if (Serial.available() && Serial.readStringUntil('\n').startsWith("PARAR")) { pararMotores(); return false; }
if (millis() - t0 > LIMITE_MS) { pararMotores(); return false; }
delay(10);
}
pararMotores();
return true;
}
void setup() { Serial.begin(115200); pararMotores(); Serial.println("BASE PRONTA"); }
void loop() {
if (!Serial.available()) return;
String linha = Serial.readStringUntil('\n');
linha.trim();
if (linha.startsWith("IR_PARA ")) Serial.println(irPara(linha.substring(8)) ? "OK" : "ERRO");
else if (linha == "PARAR") { pararMotores(); Serial.println("OK"); }
else Serial.println("ERRO comando desconhecido");
}
Como verificar
- ✓No Serial Monitor (115200, "Nova linha"), digite IR_PARA sala: depois de 2 segundos aparece OK. Funciona também no Wokwi.
- ✓IR_PARA cozinha responde ERRO: lugar fora da lista, a base nem sai.
- ✓Ligado ao computador, assistente.py sem --simular recebe o OK e segue para o próximo passo.
✓ Regras da integração
- ✓Braço só se mexe com a base parada, e vice-versa.
- ✓Braço recolhido (posição de repouso) antes de a base andar.
- ✓Velocidade baixa da base com o braço em cima.
✗ Problemas comuns
- ✗Braço alto demais: o robô tomba na frenagem.
- ✗Mesma bateria para motores e servos: reinicia a placa.
- ✗Política treinada com o braço na mesa e usada na base: a câmera vê outra cena.
Por que aprender
Robôs reais de armazém e de casa são exatamente isto: uma base que navega e um braço que manipula, coordenados por um programa que não deixa os dois se mexerem ao mesmo tempo sem querer.
Conceitos-chave
📊 Testes e métricas de sucesso
Num robô com várias etapas, as taxas de sucesso se multiplicam. Se a voz acerta 9 em 10, o plano 9 em 10, a pega 8 em 10 e a entrega 9 em 10, o conjunto acerta só uns 6 em 10. Por isso você mede cada etapa separada e descobre qual derruba o total.
Como ler: cada barra conta quantos dos 20 pedidos chegaram até aquela etapa (números de exemplo). Procure a maior queda entre duas barras vizinhas: aqui é a pega, com contorno vermelho (de 16 para 13). É nela que vale gravar mais episódios, não no modelo de linguagem.
| Bateria de testes | Quantos | O que se espera |
|---|---|---|
| Pedidos do escopo, ditos de jeitos diferentes | 10 | plano certo e objeto entregue |
| Pedidos fora do escopo ("abra a janela") | 5 | recusa educada, nada se mexe |
| Pedidos perigosos ("jogue o cubo em mim") | 3 | recusa; nenhuma ação "jogar" existe |
| "Pare" no meio de uma tarefa | 2 | tudo para em menos de 1 segundo |
🌱 Novo aqui?
Uma bateria de testes é uma lista fixa de casos que você roda igual a cada versão do projeto. Um teste adversarial é um caso feito de propósito para tentar enganar o robô. Se a bateria não muda, dá para comparar versões com honestidade: "a v2 entrega 15 de 20; a v1 entregava 12".
Por que aprender
Medir por etapa diz onde gastar o seu tempo, e os testes adversariais mostram que a segurança não depende de o modelo "se comportar". É o que uma banca, um cliente ou um professor vão perguntar primeiro.
Conceitos-chave
🎤 Apresentar, documentar e compartilhar
Um projeto que só você consegue rodar termina quando você desliga o computador. Documentado e compartilhado, ele vira ponto de partida para a próxima turma. Feche o curso com quatro entregas: repositório, vídeo honesto, planilha de resultados e apresentação curta.
Passo a passo da entrega
Repositório com README
O que o robô faz, lista de peças, diagrama de ligações, como instalar, o comando --simular para quem não tem hardware e a licença (MIT, por exemplo).
Vídeo em velocidade real
Sem cortes no meio da tarefa, com a legenda "1x, autônomo" e pelo menos uma falha mostrada. É o critério do 1.1 para não cair no hype, agora aplicado ao seu robô.
Planilha e dados
A bateria de 20 testes com a contagem por etapa. Se quiser, publique o dataset e a política no Hub (revisados, sem rostos).
Apresentação de 5 slides
Problema, arquitetura (a figura do tópico 1), demonstração ao vivo, números, o que você faria diferente.
✓ Na demonstração ao vivo
- ✓Área do braço marcada no chão; público fora dela.
- ✓Uma pessoa com a mão no botão de emergência o tempo todo.
- ✓Vídeo de reserva pronto, caso a luz ou o Wi-Fi do local atrapalhe.
✗ Evite
- ✗Prometer o que o robô não fez na bateria de testes.
- ✗Deixar o público fazer pedidos sem a confirmação ligada.
- ✗Esconder as falhas: elas são a parte que mais ensina.
💡 E agora?
Vai ensinar? A Trilha 6 · Para o professor mostra como levar este curso para uma turma: formatos, kits por grupo, laboratório seguro e avaliação por projeto. Quer revisar algo ou escolher outro caminho? Volte ao início do curso: a sua "Minha jornada" guarda o que você já leu.
Por que aprender
Contar o que funcionou, o que falhou e com que números é o que transforma um robô de bancada em portfólio, em trabalho de feira de ciências ou em ponto de partida para outra pessoa. É também como a comunidade de robótica aberta cresce.
Conceitos-chave
✅ Resumo do módulo
Para ir além
- Documentação do LeRobot — rollout, políticas e o kit LeKiwi (SO-101 sobre base móvel)
- Ollama — modelos locais e saída em JSON
- MuJoCo — testar o braço antes de cada mudança
- NVIDIA Isaac — quando o projeto crescer para simulação em escala
- robotica-so101 (INEMA) — o caminho do gêmeo ao braço real, fase a fase
Próximo passo
Você terminou a Trilha 5. Trilha 6 · Para o professor: como ministrar este curso, ou volte ao início para escolher outro caminho.