PTENES
MÓDULO 5.1 · FUNDAMENTO

🧠 Modelo de linguagem como cérebro

Um modelo de linguagem entende pedidos como "leva a garra até o copo", mas erra, inventa e não sente o mundo. Neste módulo você monta a arquitetura que aproveita o que ele faz bem sem entregar o motor a ele: o modelo escolhe, o robô executa, dentro de uma lista fechada de ações com limites conferidos no código.

6
Tópicos
Avançado
Nível
Fundamento
Tipo
PC ou Pi 5
Hardware
0%
0 de 0 tópicos lidos · Seção 1 de 6

Conteúdo detalhado

1

🧩 LLM, VLM e VLA: o que cada um faz no robô

Três siglas aparecem em todo projeto de robô com IA, e cada uma ocupa um lugar diferente. O LLM (modelo de linguagem grande) recebe texto e devolve texto: entende o pedido e decide qual ação faz sentido. O VLM (modelo de visão e linguagem) recebe uma imagem junto com o texto e responde sobre ela: "tem um copo azul à esquerda". O VLA (visão, linguagem e ação) vai um passo além: recebe imagem e instrução e devolve o próprio movimento das juntas, várias vezes por segundo.

🌱 Novo aqui?

Um modelo é um arquivo de números (os parâmetros, ou pesos) aprendidos a partir de muitos exemplos. Rodar o modelo para obter uma resposta se chama inferência. O texto entra e sai em pedaços chamados tokens (partes de palavras). Quanto mais parâmetros, em geral, melhor a resposta e maior a memória e o tempo que a inferência exige.

entramodelosai textoLLMtexto ouação escolhidaimagem+ texto VLM"copo azulà esquerda"imagem+ instruçãoVLA ângulos das juntasvárias vezes/s de cima para baixo: o modelo chega cada vez mais perto do motor

Como ler: cada linha é um tipo de modelo. À esquerda (ciano) está o que ele recebe; à direita, o que devolve. O LLM e o VLM param em palavras ou numa escolha; o VLA, com brilho, devolve movimento direto. Este módulo usa os dois primeiros como planejadores; o VLA aparece no módulo 5.4, com o SmolVLA.

💬 LLM

Traduz "vai um pouco para a frente" em andar 20 cm. Bom para entender pedidos, ruim para medir distância.

👁️ VLM

Descreve a cena da câmera. Ajuda o LLM a saber o que existe na mesa, mas pode errar cor, quantidade e posição.

🦾 VLA

Treinado com demonstrações do próprio braço. Controla as juntas, por isso precisa de dados, testes e limites ainda mais rígidos.

Por que aprender

Saber qual modelo faz o quê evita o erro mais comum de quem começa: pedir a um modelo de texto que controle um motor em tempo real. Cada tipo tem um lugar na pilha do robô, e escolher o lugar certo é metade da segurança.

Conceitos-chave

LLM
texto → texto
VLM
imagem + texto → texto
VLA
imagem + texto → movimento
Inferência
rodar o modelo
2

🛡️ Arquitetura segura: o modelo escolhe, o robô executa

O modelo de linguagem fica no alto nível: ele lê o pedido e escolhe uma ação. Tudo que vem depois é código comum, que você escreveu e testou. Um validador no Raspberry Pi ou no PC confere se a ação existe e se os números estão dentro dos limites. Só então uma linha de texto vai pela serial até o ESP32, que tem os próprios limites e a parada de emergência. Se o modelo inventar, a resposta morre no validador.

pode errarcódigo testado por vocêpedido modelovalidadorserialESP32 JSONANDAR 20limitesde novo recusa e explicaPAREação fora da listaou fora do limite

Como ler: a caixa ciano tracejada é a parte que pode errar (o modelo); a caixa teal é código determinístico, que sempre faz a mesma coisa. A seta vermelha tracejada mostra o caminho da recusa. O botão PARE age direto no ESP32, sem passar pelo modelo nem pelo Pi.

✓ Faça

  • ✓Deixe o modelo escolher entre poucas ações com nome claro.
  • ✓Confira tudo no código antes de mandar à serial.
  • ✓Repita os limites no ESP32: ele é a última barreira.
  • ✓Registre cada pedido, resposta e decisão num log.

✗ Não faça

  • ✗Deixar o modelo gerar código e executar sem revisão.
  • ✗Mandar o texto do modelo direto para a serial.
  • ✗Pedir ao modelo valores de PWM ou ângulos crus de motor.
  • ✗Depender do modelo para parar o robô numa emergência.

Por que aprender

Modelos de linguagem às vezes respondem com confiança algo errado (a isso se dá o nome de alucinação). Num chat, isso gera uma frase ruim; num robô, pode gerar uma batida. A separação entre "escolher" e "executar" deixa você trocar de modelo, local ou na nuvem, sem mexer no que garante a segurança.

Conceitos-chave

Alto nível
o quê, não o como
Validador
código que recusa
Alucinação
erro dito com confiança
Última barreira
limites no ESP32
3

📋 Lista fechada de ações em JSON (function calling)

Em vez de pedir uma resposta livre, você entrega ao modelo um cardápio: as ações que o robô sabe fazer, os parâmetros de cada uma e o formato exato da resposta. Essa ideia tem o nome de function calling (chamada de função) ou tool use. O Ollama aceita um JSON Schema no campo format e força a resposta a seguir esse molde; o campo enum lista os únicos nomes de ação permitidos.

🌱 Novo aqui?

JSON é um formato de texto para dados, com chaves e valores: {"acao": "girar", "parametros": {"graus": 90}}. Um JSON Schema é a regra que diz quais chaves existem e de que tipo é cada valor. O schema ajuda o modelo a acertar o formato, mas não garante que o valor seja seguro: 9000 graus ainda é um número válido. Por isso o validador confere os limites depois.

lista fechadaandar5 a 100 cm girar−180 a 180°garraabrir | fecharpararsem parâmetrosó isto existe andar · cm: 30✓ envia ANDAR 30girar · graus: 900✗ fora do limite pular · altura: 1✗ ação não existe

Como ler: à esquerda está o cardápio que você escreve no código. À direita, três respostas possíveis do modelo. Só a primeira vira comando; as vermelhas são recusadas pelo validador, e o robô fica parado e explica o motivo.

Objetivo: ver como fica uma resposta válida do modelo. Este é o formato que o script do tópico 4 exige; repare que motivo é só texto para o log e nunca é executado.

resposta-exemplo.json
{
  "acao": "girar",
  "parametros": { "graus": -90 },
  "motivo": "o usuário pediu para virar à esquerda"
}

Como verificar: cole o bloco num validador de JSON (ou rode python3 -m json.tool resposta-exemplo.json); ele deve ser aceito sem erro. Troque -90 por "esquerda" e veja que o tipo deixa de ser número: o validador do tópico 4 recusaria.

Por que aprender

A lista fechada transforma um problema aberto ("o que o modelo vai responder?") num problema fechado e testável ("qual das quatro ações ele escolheu, e o número está no limite?"). É o mesmo princípio que assistentes comerciais usam para chamar ferramentas, aplicado ao robô.

Conceitos-chave

Function calling
modelo escolhe ferramenta
JSON Schema
molde da resposta
enum
só estes nomes
Limite
mínimo e máximo
4

💻 Rodar um modelo local (Ollama no Pi 5 ou no PC)

O Ollama baixa modelos abertos e os serve na sua máquina, numa API HTTP em http://localhost:11434. O seu script conversa com ele como conversaria com um serviço na internet, só que nada sai do computador. Modelos na nuvem também servem para esta arquitetura (o validador é o mesmo); o curso mostra o caminho local porque funciona sem internet, sem custo por pedido e sem mandar a voz ou a imagem da sua casa para fora.

seu PC, Pi 5 ou Jetson scriptPythonOllamaporta 11434modelo na RAMESP32motores HTTPinternet:não precisa USB

Como ler: tudo dentro da moldura teal roda na sua máquina. As duas setas entre o script e o Ollama são a pergunta e a resposta pela rede local (localhost). O cabo tracejado ciano é a serial até o ESP32, a mesma do módulo 3.5.

Onde rodaTamanho de modelo que cabeNa prática
Raspberry Pi 5 (8 GB), só CPUpequenos, de 1 a 3 bilhões de parâmetrosalguns segundos por resposta curta
Pi 5 + AI HAT+ 2LLM e VLM pequenos no aceleradorconfira os modelos suportados na doc do Pi
Jetson Orin Nano Superpequenos e médios, com GPUmais rápido que o Pi, mais caro
PC com GPU NVIDIAmédios (7 a 14 bilhões) com folgaresposta quase imediata

Passo a passo

1

Instale o Ollama

Siga o instalador do seu sistema em ollama.com/download (no Linux e no Pi é um comando só). Ele sobe um serviço que fica ouvindo na porta 11434.

2

Escolha e baixe um modelo

Em ollama.com/library, filtre por modelos com suporte a "tools" e tamanho que caiba na sua memória. Baixe com ollama pull <modelo>. Os nomes e versões mudam rápido: confira a lista do dia.

3

Teste pelo terminal e instale o pyserial

Rode os comandos abaixo. Se o curl devolver um JSON com "message", o servidor está pronto para o script.

terminal
ollama pull <modelo>          # troque pelo nome escolhido
ollama list                     # o modelo deve aparecer aqui
curl http://localhost:11434/api/chat -d '{"model": "<modelo>", "stream": false,
  "messages": [{"role": "user", "content": "Responda só: ok"}]}'
python3 -m venv ~/robo-ia && source ~/robo-ia/bin/activate
pip install pyserial

Objetivo: o script completo da arquitetura segura. Ele manda o pedido e o schema ao modelo local, valida a resposta (ação na lista, parâmetros dentro dos limites, nada a mais), pede confirmação para movimentos grandes e só então envia uma linha de texto ao ESP32. Com --simular, imprime a linha em vez de enviar, para testar sem hardware.

cerebro_llm.py
#!/usr/bin/env python3
"""Cérebro com modelo local: o modelo ESCOLHE uma ação da lista fechada,
o código VALIDA e só então manda uma linha de texto ao ESP32 pela serial.
Uso: python3 cerebro_llm.py            (com ESP32)
     python3 cerebro_llm.py --simular  (sem hardware: só imprime)"""
import json, sys, time, urllib.request, urllib.error

OLLAMA_URL = "http://localhost:11434/api/chat"
MODELO = "<modelo>"          # troque pelo nome que aparece em: ollama list
PORTA = "/dev/ttyUSB0"       # USB: /dev/ttyUSB0 ou /dev/ttyACM0 · UART do Pi: /dev/serial0 · Windows: COM3
BAUD = 115200
CONFIRMAR_ACIMA_CM = 50        # andar mais que isso pede "s" no teclado

# Lista fechada: ação -> {parâmetro: (mínimo, máximo)} ou {parâmetro: [valores permitidos]}
ACOES = {
    "andar": {"cm": (5, 100)},
    "girar": {"graus": (-180, 180)},
    "garra": {"estado": ["abrir", "fechar"]},
    "parar": {},
}

ESQUEMA = {
    "type": "object",
    "properties": {
        "acao": {"type": "string", "enum": list(ACOES)},
        "parametros": {"type": "object"},
        "motivo": {"type": "string"},
    },
    "required": ["acao", "parametros"],
}

SISTEMA = (
    "Você controla um robô pequeno. Escolha UMA ação e responda só em JSON.\n"
    "Ações: andar {cm: 5 a 100, negativo não existe}; girar {graus: -180 a 180, "
    "negativo = esquerda}; garra {estado: abrir ou fechar}; parar {}.\n"
    "Se o pedido não couber em nenhuma ação, ou for perigoso, use parar e explique no motivo."
)

def perguntar_modelo(pedido):
    """Manda o pedido ao Ollama e devolve o dicionário da resposta (ou levanta erro)."""
    corpo = {
        "model": MODELO,
        "messages": [{"role": "system", "content": SISTEMA},
                     {"role": "user", "content": pedido}],
        "stream": False,           # uma resposta inteira, não pedaços
        "format": ESQUEMA,          # força o molde JSON
        "options": {"temperature": 0},
    }
    req = urllib.request.Request(OLLAMA_URL, data=json.dumps(corpo).encode(),
                                 headers={"Content-Type": "application/json"})
    with urllib.request.urlopen(req, timeout=120) as r:
        dados = json.loads(r.read())
    return json.loads(dados["message"]["content"])

def validar(resp):
    """Devolve (ok, linha_ou_motivo, precisa_confirmar). Nunca confia no modelo."""
    if not isinstance(resp, dict):
        return False, "resposta não é um objeto JSON", False
    acao = resp.get("acao")
    if acao not in ACOES:
        return False, f"ação '{acao}' não está na lista", False
    params = resp.get("parametros") or {}
    if not isinstance(params, dict):
        return False, "parâmetros não são um objeto", False
    sobra = set(params) - set(ACOES[acao])
    if sobra:
        return False, f"parâmetro desconhecido: {', '.join(sorted(sobra))}", False
    valores = []
    for nome, regra in ACOES[acao].items():
        if nome not in params:
            return False, f"falta o parâmetro '{nome}'", False
        v = params[nome]
        if isinstance(regra, list):                      # valores permitidos
            if v not in regra:
                return False, f"'{v}' não é permitido para {nome}", False
            valores.append(str(v).upper())
        else:                                            # faixa numérica
            if isinstance(v, bool) or not isinstance(v, (int, float)):
                return False, f"{nome} precisa ser número", False
            minimo, maximo = regra
            if not (minimo <= v <= maximo):
                return False, f"{nome}={v} fora do limite ({minimo} a {maximo})", False
            valores.append(str(int(round(v))))
    linha = " ".join([acao.upper()] + valores)         # ex.: ANDAR 30 · GARRA ABRIR · PARAR
    confirmar = acao == "andar" and params["cm"] > CONFIRMAR_ACIMA_CM
    return True, linha, confirmar

def abrir_serial(simular):
    if simular:
        return None
    import serial                                       # pip install pyserial
    s = serial.Serial(PORTA, BAUD, timeout=2)
    time.sleep(2)                                       # abrir a porta reinicia o ESP32: espere
    s.reset_input_buffer()
    return s

def enviar(s, linha):
    if s is None:
        return f"(simulado) {linha}"
    s.write((linha + "\n").encode())
    return s.readline().decode(errors="replace").strip() or "(sem resposta do ESP32)"

def main():
    simular = "--simular" in sys.argv
    s = abrir_serial(simular)
    print("Cérebro pronto. Escreva um pedido (ou 'sair').")
    while True:
        pedido = input("Você: ").strip()
        if pedido.lower() in ("sair", "exit"):
            break
        if not pedido:
            continue
        try:
            resp = perguntar_modelo(pedido)
        except urllib.error.URLError as e:
            print(f"Ollama não respondeu ({e}). Ele está rodando? Teste: ollama list"); continue
        except (json.JSONDecodeError, KeyError) as e:
            print(f"Recusado: resposta ilegível ({e})."); continue
        print("Modelo:", json.dumps(resp, ensure_ascii=False))
        ok, linha, confirmar = validar(resp)
        if not ok:
            print("Recusado:", linha); continue
        if confirmar and input(f"Confirma '{linha}'? (s/n) ").strip().lower() != "s":
            print("Cancelado por você."); continue
        print("ESP32:", enviar(s, linha))
    if s:
        s.write(b"PARAR\n"); s.close()

if __name__ == "__main__":
    main()

Como verificar

  • ✓Com --simular, "anda trinta centímetros" mostra o JSON do modelo e "ESP32: (simulado) ANDAR 30".
  • ✓"anda dois metros" é recusado (cm fora do limite) ou vira parar; "anda 80 cm" pede confirmação antes.
  • ✓"faz um café" termina em recusa ou em PARAR, nunca numa ação inventada.
  • ✗"Ollama não respondeu"? O serviço está parado ou o nome do modelo não bate com ollama list.

💡 Teste o validador sem modelo

No Python interativo, rode from cerebro_llm import validar e chame validar({"acao": "girar", "parametros": {"graus": 900}}). Escreva uns dez casos ruins (ação inexistente, texto no lugar de número, parâmetro sobrando). Se todos forem recusados, o seu robô está protegido contra qualquer modelo que você plugar depois.

Por que aprender

Com o modelo local você testa à vontade, sem cota nem conta, e o robô continua funcionando sem Wi-Fi. E, como o validador fica no seu código, trocar o Ollama por um modelo na nuvem é trocar só a função perguntar_modelo.

Conceitos-chave

Ollama
servidor local de modelos
localhost:11434
a API na sua máquina
temperature 0
resposta mais previsível
--simular
testar sem hardware
5

👁️ VLM: "o que você vê?" pela câmera

Um VLM recebe uma foto e uma pergunta. No Ollama, a imagem vai em base64 no campo images da mensagem. O uso seguro é o mesmo de antes: o VLM descreve, e essa descrição entra como contexto no pedido ao LLM (ou no mesmo modelo, se ele aceitar imagem e ferramentas). Ele não manda em motor. Para posição exata, o detector do módulo 3.3 continua mais confiável; o VLM ajuda quando a pergunta é aberta: "tem alguma coisa no caminho?", "qual objeto é vermelho?".

Objetivo: tirar uma foto da câmera USB (ou da Camera Module 3 via OpenCV) e perguntar ao VLM local o que há na cena. Precisa de um modelo com visão: na biblioteca do Ollama, filtre por "vision".

o_que_ve.py
#!/usr/bin/env python3
"""Tira uma foto e pergunta ao VLM local o que há nela. pip install opencv-python"""
import base64, json, sys, urllib.request
import cv2

MODELO_VISAO = "<modelo-de-visao>"   # um modelo com 'vision' em ollama.com/library
PERGUNTA = sys.argv[1] if len(sys.argv) > 1 else "Liste os objetos sobre a mesa, com cor e posição (esquerda, centro, direita)."

cam = cv2.VideoCapture(0)                # 0 = primeira câmera
for _ in range(5):                       # descarta quadros escuros do começo
    ok, quadro = cam.read()
cam.release()
if not ok:
    sys.exit("Câmera não abriu: confira o índice (0, 1...) e o cabo.")
quadro = cv2.resize(quadro, (640, 480))   # imagem menor = resposta mais rápida
cv2.imwrite("foto.jpg", quadro)
_, jpg = cv2.imencode(".jpg", quadro)

corpo = {
    "model": MODELO_VISAO, "stream": False, "options": {"temperature": 0},
    "messages": [{"role": "user", "content": PERGUNTA + " Responda em português, em até 3 frases.",
                  "images": [base64.b64encode(jpg.tobytes()).decode()]}],
}
req = urllib.request.Request("http://localhost:11434/api/chat", data=json.dumps(corpo).encode(),
                             headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=180) as r:
    print(json.loads(r.read())["message"]["content"])

Como verificar: o arquivo foto.jpg aparece na pasta e o terminal mostra uma descrição curta da cena. Ponha dois objetos de cores diferentes na mesa e troque-os de lado: a descrição deve acompanhar. Se ela errar, você acabou de ver por que o VLM não pode mandar no motor.

✓ Bom uso do VLM

  • ✓Dar contexto ao LLM: "na mesa há um copo azul e uma bola".
  • ✓Responder perguntas abertas do usuário sobre a cena.
  • ✓Conferir depois da ação: "a garra está segurando algo?".

✗ Mau uso do VLM

  • ✗Pedir coordenadas em milímetros e mandar o braço até lá.
  • ✗Usar como sensor de segurança ("tem gente perto?").
  • ✗Chamar a cada quadro de vídeo: é lento demais para isso.

Por que aprender

Juntar visão e linguagem deixa o robô responder a pedidos que citam o mundo ("pega o vermelho"). Saber onde o VLM é forte e onde ele erra prepara você para o VLA do módulo 5.4, que leva a mesma ideia até o movimento.

Conceitos-chave

images (base64)
foto dentro do JSON
Contexto
descrição que ajuda o LLM
Detector x VLM
posição x significado
Resolução
menor = mais rápido
6

🚨 Travas: limites, confirmação e parada de emergência

Segurança em robô com IA é feita em camadas, e cada camada não confia na anterior. O validador do Pi é a primeira. O ESP32 repete os limites e recusa qualquer linha que não entenda. Um watchdog de comando para os motores se nenhuma ordem chegar em pouco tempo. E um botão de parada físico corta o movimento sem depender de software algum.

4 · botão de parada (corta a energia dos motores) 3 · watchdog: sem comando novo, para2 · ESP32 confere limites de novo 1 · validador no Pi / PCmotor

Como ler: o motor está no centro; para que ele se mova, nenhuma das quatro camadas ao redor pode ter barrado o comando. A camada vermelha, por fora de tudo, é a única que funciona mesmo com o programa travado: é física. Se uma camada falhar, a de fora ainda segura.

Objetivo: o lado ESP32 do protocolo de linhas de texto (o mesmo do módulo 3.5), com limites próprios, resposta OK/ERRO, watchdog de 2 segundos e um botão de parada no GPIO 13. As funções de motor só imprimem; troque pelo código dos seus motores (Trilha 1).

esp32_executor.ino
// Executor seguro: recebe "ANDAR 30", "GIRAR -90", "GARRA ABRIR", "PARAR" (ESP32, pacote esp32 3.x)
const int PINO_PARADA = 13;              // botão para o GND (usa pull-up interno)
const unsigned long WATCHDOG_MS = 2000;  // sem comando por 2 s: para
unsigned long ultimoComando = 0;
bool movendo = false;

void pararMotores() { movendo = false; Serial.println("# motores parados"); }  // troque pelo seu driver
void andar(int cm)    { movendo = true; Serial.printf("# andando %d cm\n", cm); }
void girar(int graus) { movendo = true; Serial.printf("# girando %d graus\n", graus); }
void garra(bool abrir) { Serial.println(abrir ? "# garra abrindo" : "# garra fechando"); }

void setup() {
  Serial.begin(115200);
  pinMode(PINO_PARADA, INPUT_PULLUP);
  pararMotores();
  Serial.println("PRONTO");
}

void loop() {
  if (digitalRead(PINO_PARADA) == LOW) { pararMotores(); while (digitalRead(PINO_PARADA) == LOW) delay(10); }
  if (movendo && millis() - ultimoComando > WATCHDOG_MS) pararMotores();
  if (!Serial.available()) return;
  String linha = Serial.readStringUntil('\n'); linha.trim();
  if (linha.length() == 0 || linha.length() > 32) { Serial.println("ERRO linha invalida"); return; }
  ultimoComando = millis();
  int esp = linha.indexOf(' ');
  String cmd = esp < 0 ? linha : linha.substring(0, esp);
  String arg = esp < 0 ? "" : linha.substring(esp + 1);
  if (cmd == "PARAR") { pararMotores(); Serial.println("OK PARAR"); }
  else if (cmd == "ANDAR") { int v = arg.toInt(); if (v < 5 || v > 100) Serial.println("ERRO limite"); else { andar(v); Serial.println("OK ANDAR"); } }
  else if (cmd == "GIRAR") { int v = arg.toInt(); if (v < -180 || v > 180) Serial.println("ERRO limite"); else { girar(v); Serial.println("OK GIRAR"); } }
  else if (cmd == "GARRA" && (arg == "ABRIR" || arg == "FECHAR")) { garra(arg == "ABRIR"); Serial.println("OK GARRA"); }
  else { pararMotores(); Serial.println("ERRO comando desconhecido"); }
}

Como verificar

  • ✓No Serial Monitor (115200, fim de linha "Nova linha"), "ANDAR 30" responde "OK ANDAR" e, 2 s depois, "# motores parados".
  • ✓"ANDAR 500" responde "ERRO limite" mesmo que o Pi tenha falhado em barrar.
  • ✓Com o botão do GPIO 13 apertado, qualquer movimento para na hora. Teste também no Wokwi.

💡 Parada de verdade é física

O botão no GPIO depende do programa estar rodando. Em robô com força (braço, base pesada), coloque também uma chave em série com a alimentação dos motores, ao alcance da mão. Antes de cada teste, aperte-a uma vez para ter certeza de que funciona.

Por que aprender

Os módulos seguintes põem um braço que pega objetos e uma voz que dá ordens. Com as quatro camadas prontas, qualquer modelo que você testar, local ou na nuvem, grande ou pequeno, opera dentro de uma caixa que você controla.

Conceitos-chave

Camadas
nenhuma confia na outra
Watchdog
sem ordem, para
Confirmação
humano aprova o grande
Parada física
corta a energia

✅ Resumo do módulo

✓
LLM, VLM e VLA: texto, imagem com texto e movimento; cada um tem um lugar na pilha.
✓
O modelo escolhe, o robô executa: o código valida antes de qualquer movimento.
✓
Lista fechada em JSON: enum de ações, limites numéricos, nada a mais.
✓
Ollama local: API em localhost:11434, sem internet; nuvem também serve com o mesmo validador.
✓
Quatro camadas de trava: validador, limites no ESP32, watchdog e parada física.

Para ir além

Próximo módulo

5.2 · Voz local: ouvir com o Whisper e falar com o Piper, ligando a voz ao cérebro deste módulo.