INEMA.CLUB
Robôs para montar · visão local

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.

Mapa visto de cima: circuito de fita preta com quatro marcos, um obstáculo no lado direito e o caminho de desvio tracejado que volta para a linha AB CD caixa área vigiada fita preta no chão claro tracejado amarelo: desvio · A–D: marcos
A rota é uma fita isolante preta. Faixas atravessadas (os marcos A, B, C, D) dizem ao robô em que trecho da ronda ele está.

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.

Estados: patrulhando, imprevisto, desvio, volta à rota e alerta, com as condições de passagem entre eles PATRULHANDO obstáculo < 20 cm IMPREVISTO para e espera 3 s liberou ainda bloqueado DESVIO contorna pela direita VOLTA À ROTA anda até achar a linha achou a linha 5 s sem linha ALERTA para, apita, pede ajuda
O ALERTA também é ativado pelo PC quando a câmera vê pessoa sem autorização. Para sair dele, alguém libera o robô.

A decisão da câmera (no PC)

Decisão da câmera: se há pessoa, confere crachá autorizado ou horário livre; se não, alerta Quadro da câmera YOLO procura pessoa Tem pessoa? não:segue sim Crachá autorizado ou horário livre? sim nada acontece não: ALERTA robô para e apita
O YOLO só diz "há uma pessoa", nunca "quem é". A autorização vem de um objeto (o crachá) ou do relógio.

Peças

Lista de compras

PeçaPara que servePreço aprox. (out/2026, a conferir)
ESP32 DevKit (30 ou 38 pinos)Controla motores e sensores, recebe o alerta por Wi-FiR$ 35–50
Ponte H TB6612FNGLiga 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ãoR$ 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 PCR$ 45–70
Suporte para 4 pilhas AA + pilhasEnergia dos motores (6 V)R$ 25–40
Power bank USBEnergia do ESP32 e da câmerao que você já tem
Buzzer ativo 3,3–5 V, fita isolante pretaApito do alerta e a rotaR$ 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

Circuito: pilhas na ponte H, power bank no ESP32, ponte H nos motores, sensores IR, ultrassônico com divisor de tensão e buzzer no ESP32; ESP32-CAM manda vídeo ao PC por Wi-Fi Pilhas 4×AA 6 V, só motores Power bank USB 5 V VMUSB TB6612FNG ponte H ESP32 cérebro do robô Motores E e D AO1/AO2 · BO1/BO2 2× IR DO → 34 e 35 HC-SR04 VCC → 5V TRIG → 18 ECHO → 1 kΩ → 19 19 → 2 kΩ → GND Buzzer → 23 ESP32-CAM 5 V próprio Wi-Fi PC com YOLO manda o alerta GND de pilhas, ponte H e ESP32 ligados juntos
O ECHO do HC-SR04 sai em 5 V. O divisor de 1 kΩ e 2 kΩ baixa para cerca de 3,3 V, o limite do ESP32.
TB6612FNGLiga emTB6612FNGLiga em
AIN1GPIO 25BIN1GPIO 14
AIN2GPIO 26BIN2GPIO 32
PWMAGPIO 27PWMBGPIO 13
STBYGPIO 33VCC3V3 do ESP32
VM+ das pilhas (6 V)GND− das pilhas e GND do ESP32
AO1, AO2motor esquerdoBO1, BO2motor direito

Passo a passo

Montando a ronda

  1. Monte o chassi e teste cada motor sozinho com o código. Se um girar ao contrário, inverta os dois fios dele.
  2. Fixe os sensores IR na frente, a 1 cm do chão, separados por uns 3 cm (a fita fica entre eles).
  3. 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.
  4. Ajuste o potenciômetro de cada módulo IR até o LED dele mudar ao passar da fita para o chão.
  5. Prenda o HC-SR04 na frente, olhando reto, a uns 8 cm do chão.
  6. 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.
  7. 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))
  8. 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:

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

O que aconteceCausa provávelO que mudar
Sai da fita nas curvasRápido demais ou curva fechadaBaixe a velocidade (150 → 110) ou alargue a curva
Vira para o lado erradoSeu módulo IR dá LOW na fita pretaInverta a leitura: !digitalRead(...)
ESP32 reinicia quando os motores ligamMotores puxando da mesma fonte ou sem GND comumMotores só nas pilhas; GND comum entre tudo
Distância sempre 999ECHO sem divisor ou fios trocadosConfira TRIG 18, ECHO 19 e os resistores
ESP32-CAM reinicia sem pararFonte fraca5 V com 2 A, cabo curto; não puxe do 3V3
Não volta para a rota após o desvioTempos de giro erradosMeç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 contribpip uninstall opencv-python e reinstale opencv-contrib-python
Alerta com crachá à vistaCrachá pequeno, longe ou tortoImprima 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

Câmera exige aviso. Coloque uma placa "área monitorada por câmera" e conte para todos da casa. Aponte só para dentro da sua casa ou da área do projeto, nunca para a rua ou o vizinho. Na feira, nada de gravar ou publicar o rosto de visitantes.

Para ir além

Próximos passos e cursos