O robô vigia que faz a ronda sozinho
Ele percorre sempre o mesmo circuito, para quando algo bloqueia o caminho, contorna e volta para a rota. A câmera procura pessoa desconhecida na área e dispara um alerta. A detecção roda no seu computador, sem nuvem, e quem está autorizado é reconhecido pelo crachá, não pelo rosto.
Como funciona
Uma ronda, dois cérebros
O robô (um ESP32 com dois motores) cuida do corpo: segue uma fita preta no chão, mede a distância até obstáculos e decide desviar. A câmera manda o vídeo por Wi-Fi para o PC, que roda o YOLO, um modelo de visão que diz "há uma pessoa aqui". Se a pessoa não mostrar um crachá autorizado, o PC manda o robô parar e apitar.
A máquina de estados do robô
O robô está sempre em um estado e só muda quando um sensor manda. Isso deixa o código organizado e fácil de testar: você testa um estado de cada vez.
A decisão da câmera (no PC)
Peças
Lista de compras
| Peça | Para que serve | Preço aprox. (out/2026, a conferir) |
|---|---|---|
| ESP32 DevKit (30 ou 38 pinos) | Controla motores e sensores, recebe o alerta por Wi-Fi | R$ 35–50 |
| Ponte H TB6612FNG | Liga os motores nos dois sentidos com controle de velocidade. Perde menos tensão que a L298N. | R$ 15–25 |
| Chassi 2WD (2 motores TT, rodas, roda boba) | O corpo do robô | R$ 50–80 |
| 2 sensores IR TCRT5000 (módulo) | Enxergam a fita preta no chão | R$ 8–15 cada |
| Sensor ultrassônico HC-SR04 + resistores 1 kΩ e 2 kΩ | Mede a distância até obstáculos. Os resistores protegem o ESP32. | R$ 10–15 |
| ESP32-CAM com base USB (ou celular velho) | Manda o vídeo para o PC | R$ 45–70 |
| Suporte para 4 pilhas AA + pilhas | Energia dos motores (6 V) | R$ 25–40 |
| Power bank USB | Energia do ESP32 e da câmera | o que você já tem |
| Buzzer ativo 3,3–5 V, fita isolante preta | Apito do alerta e a rota | R$ 8 |
Só tem L298N? Funciona, mas ela "come" cerca de 2 V: com 6 V de pilhas, os motores recebem uns 4 V e ficam fracos. Nesse caso use 6 pilhas (9 V) ou troque pela TB6612FNG. No sensor de distância, um VL53L0X (laser ToF, 3,3 V) é alternativa mais precisa ao HC-SR04.
Ligação
O circuito
| TB6612FNG | Liga em | TB6612FNG | Liga em |
|---|---|---|---|
| AIN1 | GPIO 25 | BIN1 | GPIO 14 |
| AIN2 | GPIO 26 | BIN2 | GPIO 32 |
| PWMA | GPIO 27 | PWMB | GPIO 13 |
| STBY | GPIO 33 | VCC | 3V3 do ESP32 |
| VM | + das pilhas (6 V) | GND | − das pilhas e GND do ESP32 |
| AO1, AO2 | motor esquerdo | BO1, BO2 | motor direito |
Passo a passo
Montando a ronda
- Monte o chassi e teste cada motor sozinho com o código. Se um girar ao contrário, inverta os dois fios dele.
- Fixe os sensores IR na frente, a 1 cm do chão, separados por uns 3 cm (a fita fica entre eles).
- Cole a rota com fita isolante de 19 mm em chão claro. Curvas largas (raio de 30 cm ou mais) e 4 faixas atravessadas como marcos.
- Ajuste o potenciômetro de cada módulo IR até o LED dele mudar ao passar da fita para o chão.
- Prenda o HC-SR04 na frente, olhando reto, a uns 8 cm do chão.
- Grave o exemplo CameraWebServer na ESP32-CAM (Arduino IDE: Exemplos › ESP32 › Camera) e anote o endereço do vídeo, por exemplo
http://192.168.0.41:81/stream. - Imprima os crachás ArUco (dicionário 4×4, números 7 e 12, por exemplo) em 8 × 8 cm. Quem é da casa usa o crachá à vista. Para gerar a imagem de cada crachá, rode no PC:
import cv2 d = cv2.aruco.getPredefinedDictionary(cv2.aruco.DICT_4X4_50) for n in (7, 12): cv2.imwrite(f"cracha{n}.png", cv2.aruco.generateImageMarker(d, n, 400)) - Rode o programa do PC e faça a primeira ronda com você mesmo, sem crachá, para ver o alerta.
Código
O robô (Arduino, ESP32)
// Robô vigia - ESP32 + TB6612FNG + 2 IR + HC-SR04 + buzzer
#include <WiFi.h>
#include <WebServer.h>
#include "segredos.h" // WIFI_NOME e WIFI_SENHA (não vai para o GitHub)
const int AIN1 = 25, AIN2 = 26, PWMA = 27; // motor esquerdo
const int BIN1 = 14, BIN2 = 32, PWMB = 13; // motor direito
const int STBY = 33, IR_ESQ = 34, IR_DIR = 35;
const int TRIG = 18, ECHO = 19, BUZZER = 23;
enum Estado { PATRULHA, IMPREVISTO, DESVIO, VOLTA_LINHA, ALERTA };
Estado estado = PATRULHA;
unsigned long desde = 0; // quando entrou no estado
WebServer servidor(80);
void mudar(Estado e) { estado = e; desde = millis(); }
void motores(int esq, int dir) { // de -255 a 255
digitalWrite(AIN1, esq > 0); digitalWrite(AIN2, esq < 0);
analogWrite(PWMA, abs(esq));
digitalWrite(BIN1, dir > 0); digitalWrite(BIN2, dir < 0);
analogWrite(PWMB, abs(dir));
}
float distanciaCm() {
digitalWrite(TRIG, HIGH); delayMicroseconds(10); digitalWrite(TRIG, LOW);
long us = pulseIn(ECHO, HIGH, 25000); // espera no máximo 25 ms
return us == 0 ? 999 : us / 58.0; // 0 = nada à frente
}
void setup() {
int saidas[] = {AIN1, AIN2, PWMA, BIN1, BIN2, PWMB, STBY, TRIG, BUZZER};
for (int p : saidas) pinMode(p, OUTPUT);
pinMode(IR_ESQ, INPUT); pinMode(IR_DIR, INPUT); pinMode(ECHO, INPUT);
digitalWrite(STBY, HIGH); // acorda a ponte H
WiFi.begin(WIFI_NOME, WIFI_SENHA);
servidor.on("/alerta", [] { mudar(ALERTA); servidor.send(200, "text/plain", "ok"); });
servidor.on("/libera", [] { digitalWrite(BUZZER, LOW); mudar(PATRULHA);
servidor.send(200, "text/plain", "ok"); });
servidor.begin();
}
void loop() {
servidor.handleClient();
bool esq = digitalRead(IR_ESQ), dir = digitalRead(IR_DIR); // HIGH = fita preta
float d = distanciaCm();
unsigned long t = millis() - desde;
switch (estado) {
case PATRULHA:
if (d < 20) { motores(0, 0); mudar(IMPREVISTO); }
else if (esq && !dir) motores(60, 160); // fita à esquerda: vira à esquerda
else if (dir && !esq) motores(160, 60); // fita à direita: vira à direita
else motores(150, 150); // reto (ou passando num marco)
break;
case IMPREVISTO: // pode ser alguém passando
if (d > 30) mudar(PATRULHA);
else if (t > 3000) mudar(DESVIO);
break;
case DESVIO: // contorna pela direita
if (t < 500) motores(160, -160); // gira ~90° à direita
else if (t < 1700) motores(150, 150); // afasta da rota
else if (t < 2200) motores(-160, 160); // gira ~90° à esquerda
else if (t < 3600) motores(150, 150); // passa ao lado do obstáculo
else if (t < 4100) motores(-160, 160); // aponta de volta para a rota
else mudar(VOLTA_LINHA);
break;
case VOLTA_LINHA:
motores(140, 140);
if (esq || dir) { motores(160, -160); delay(250); mudar(PATRULHA); }
else if (t > 5000) { motores(0, 0); mudar(ALERTA); } // perdido
break;
case ALERTA: // parado e apitando
motores(0, 0);
digitalWrite(BUZZER, (millis() / 300) % 2);
break;
}
}
Os tempos do DESVIO (500, 1700, 2200…) dependem dos seus motores, das pilhas e do chão. Meça quanto tempo seu robô leva para girar 90° e ajuste. Esse é um ótimo item para o caderno de testes.
O PC (Python, YOLO local)
# vigia_pc.py - olha a câmera e avisa se há pessoa sem autorização.
# pip install ultralytics opencv-contrib-python requests
import cv2, time, datetime, requests
from ultralytics import YOLO
CAMERA = "http://192.168.0.41:81/stream" # ESP32-CAM (ou app de câmera do celular)
ROBO = "http://192.168.0.40" # endereço do robô na rede da casa
CRACHAS = {7, 12} # números ArUco autorizados
HORARIO_LIVRE = range(7, 19) # das 7h às 18h59 não há alerta
modelo = YOLO("yolo11n.pt") # modelo pequeno, roda no PC, sem nuvem
leitor = cv2.aruco.ArucoDetector(
cv2.aruco.getPredefinedDictionary(cv2.aruco.DICT_4X4_50))
cam = cv2.VideoCapture(CAMERA)
ultimo = 0
while True:
ok, quadro = cam.read()
if not ok:
time.sleep(1)
continue
if datetime.datetime.now().hour in HORARIO_LIVRE:
continue
pessoas = modelo(quadro, classes=[0], conf=0.5, verbose=False)[0].boxes
if len(pessoas) == 0: # classe 0 = pessoa
continue
cinza = cv2.cvtColor(quadro, cv2.COLOR_BGR2GRAY)
_, ids, _ = leitor.detectMarkers(cinza)
autorizado = ids is not None and any(int(i) in CRACHAS for i in ids.flatten())
if not autorizado and time.time() - ultimo > 30: # 1 alerta a cada 30 s
ultimo = time.time()
requests.get(ROBO + "/alerta", timeout=2)
print(time.strftime("%H:%M:%S"), "ALERTA: pessoa sem crachá na área")
# Nenhuma imagem é salva. O quadro some a cada volta do laço.
Para receber o alerta com foto no celular, painel e zonas proibidas, use a página de vigilância com câmera (S4): ela usa a mesma ideia de detecção local.
Quem é autorizado
Crachá e relógio, não rosto
Seria tentador usar reconhecimento facial para saber quem é da casa. Este projeto não usa, por três motivos:
- É dado sensível. A LGPD (Lei 13.709/2018, art. 5º, II, e art. 11) trata dado biométrico, como o rosto, como dado pessoal sensível. Exige consentimento específico e um cuidado que um projeto de feira não consegue garantir.
- Visitantes não concordaram. Cadastrar e reconhecer o rosto de quem passa pela casa ou pelo estande, sem pedir, é invasivo.
- Crachá é simples e reversível. O marcador ArUco identifica o crachá, não o corpo da pessoa. Perdeu? Tira o número da lista e imprime outro. Nada biométrico fica guardado.
A outra regra é o horário permitido: durante o dia, quando a família circula, o robô patrulha mas não alerta. Você pode combinar as duas: fora do horário, só quem está com crachá passa sem alerta.
Teste e melhore
Checklist e erros comuns
- Quantas voltas completas ele dá sem sair da fita? (meta da v1: 3 voltas)
- Com uma caixa no caminho, em quantas de 5 tentativas ele contorna e volta para a linha?
- Em 10 passagens sem crachá, quantos alertas certos? Em 10 com crachá, quantos alertas falsos?
- Quanto tempo entre a pessoa entrar na câmera e o robô apitar?
| O que acontece | Causa provável | O que mudar |
|---|---|---|
| Sai da fita nas curvas | Rápido demais ou curva fechada | Baixe a velocidade (150 → 110) ou alargue a curva |
| Vira para o lado errado | Seu módulo IR dá LOW na fita preta | Inverta a leitura: !digitalRead(...) |
| ESP32 reinicia quando os motores ligam | Motores puxando da mesma fonte ou sem GND comum | Motores só nas pilhas; GND comum entre tudo |
| Distância sempre 999 | ECHO sem divisor ou fios trocados | Confira TRIG 18, ECHO 19 e os resistores |
| ESP32-CAM reinicia sem parar | Fonte fraca | 5 V com 2 A, cabo curto; não puxe do 3V3 |
| Não volta para a rota após o desvio | Tempos de giro errados | Meça o giro de 90° e ajuste; aumente o tempo do VOLTA_LINHA |
| Erro "cv2 não tem aruco" | O ultralytics instalou o opencv-python, que briga com o contrib | pip uninstall opencv-python e reinstale opencv-contrib-python |
| Alerta com crachá à vista | Crachá pequeno, longe ou torto | Imprima maior (10 cm) e com borda branca |
Registre a versão 1 → versão 2 no caderno de testes. Exemplo: "v1: 1 volta e saiu na curva B · baixei para 110 · v2: 3 voltas, 2/5 desvios". Na versão 3, troque o tempo fixo do desvio por encoders nas rodas (distância real) ou um segundo sensor de distância apontado para o lado.
Segurança e privacidade
Antes de ligar a câmera
- Motores: mantenha dedos, cabelo e cabos longe das rodas. Teste com o robô suspenso antes de pôr no chão.
- Energia: pilhas AA e power bank. Nada ligado na tomada dentro do robô. Se usar bateria Li-ion (18650), só com placa de proteção, nunca carregue sem ninguém olhando e não deixe bateria solta junto de moedas ou chaves.
- ESP32-CAM esquenta: deixe espaço para o ar circular.
- Rede da casa: os comandos
/alertae/liberafuncionam só dentro da rede local. Não abra portas do roteador para a internet.
Para ir além
Próximos passos e cursos
- Mapa de verdade: com LiDAR e ROS 2, o robô monta o mapa e navega sem fita. Está na trilha 4 do curso Robótica do Zero ao Expert.
- Câmera e YOLO a fundo: trilha 3 do mesmo curso (Raspberry Pi, OpenCV, YOLO).
- Modelos que veem e agem: curso VLA: visão, linguagem e ação.
- Fale com o vigia: junte com o assistente de voz ("vigia, começa a ronda").