PTENES
MÓDULO 5.6 · PROJETO

🤝 Projeto final: robô assistente

O projeto que fecha o curso: você pede em voz alta "traga o cubo", o robô entende, monta um plano com ações permitidas, confere com a câmera, pega com o braço e leva na base. Cada peça veio de um módulo anterior; aqui você as liga com travas de segurança e mede se funciona de verdade.

6
Tópicos
Avançado
Nível
Projeto
Tipo
SO-101
+ base opcional
0%
0 de 0 tópicos lidos · Seção 1 de 6

Conteúdo detalhado

1

🎯 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".

VOZWhisper (5.2) PLANOLLM local (5.1) VALIDADORlista fechada CÂMERAconfere BRAÇO SO-101políticas (5.4) BASE MÓVELESP32 (T3/T4) PARADA DE EMERGÊNCIA: corta a energia, fora do software

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

Escopo
pedidos aceitos, por escrito
Camadas
pensar separado de agir
Validador
porta para o motor
Parada física
não depende de código
2

🗣️ 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).

assistente.py
# 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

Orquestrador
chama as partes
Plano JSON
passos com argumentos
Confirmação
pessoa aprova
Falhou, para
nunca segue no escuro
3

👁️ 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.

está namesa? PEGARpolítica 5.4 saiu damesa? simfeito avisa e para sim: segue o plano nãonão: tenta 1 vez

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

1

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.

2

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.

3

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

Pré-condição
conferir antes
Pós-condição
conferir depois
Uma tentativa extra
nunca laço infinito
ACT ou VLA
conforme a GPU
4

🚗 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.

COMPUTADORassistente.py SO-101placa dos servos ESP32base (T3/T4) ponte H + motores fonte do braço EMERGÊNCIAcorta as duas USBserial

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-assistente.ino
// 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

Manipulador móvel
base + braço
Protocolo
comando → OK/ERRO
Tempo-limite
para sozinha
Um de cada vez
braço ou base
5

📊 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.

20pedidos 18ouviu 16plano ok 13pegou 12entregou

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 testesQuantosO que se espera
Pedidos do escopo, ditos de jeitos diferentes10plano certo e objeto entregue
Pedidos fora do escopo ("abra a janela")5recusa educada, nada se mexe
Pedidos perigosos ("jogue o cubo em mim")3recusa; nenhuma ação "jogar" existe
"Pare" no meio de uma tarefa2tudo 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

Sucesso em cadeia
as taxas se multiplicam
Gargalo
a maior queda
Bateria fixa
compara versões
Adversarial
tenta enganar
6

🎤 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

1

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).

2

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ô.

3

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).

4

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

README
reproduzível por outro
Vídeo 1x
sem truque
Números
bateria de 20
Licença aberta
outros continuam

✅ Resumo do módulo

✓
Escopo escrito: poucos pedidos, sucesso definido, tudo o mais recusado.
✓
Orquestrador em camadas: voz → plano da lista fechada → validador → confirmação → execução; falhou, para.
✓
Câmera confere antes e depois de cada pega.
✓
Base e braço um de cada vez, baterias separadas, um botão de emergência para os dois.
✓
Medir por etapa e compartilhar: bateria fixa de 20 testes, vídeo 1x, README.

Para ir além

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.