Construir uma fechadura inteligente caseira é um dos projetos DIY mais procurados por entusiastas de casa inteligente. Com componentes simples como Arduino, servo motor e módulo Wi-Fi, é possível criar um sistema de controle de acesso por smartphone gastando apenas R$ 150-300, uma fração do preço de fechaduras comerciais que custam R$ 1.500+.
Visão Geral do Projeto
O Que Vamos Construir
Sistema Completo:
- Fechadura controlada por servo motor
- Controle via smartphone (Android/iOS)
- Interface web para múltiplos usuários
- Histórico de acessos
- Modo manual de emergência
Funcionalidades:
- Abertura remota: De qualquer lugar com internet
- Códigos temporários: Para visitantes e prestadores
- Log de acesso: Histórico completo de entradas
- Notificações: Alertas em tempo real
- Backup manual: Chave física sempre funcional
Nível de Dificuldade
Conhecimentos Necessários:
- Eletrônica básica (uso de protoboard)
- Programação Arduino iniciante
- Soldagem simples (opcional)
- Uso de ferramentas básicas
Tempo de Construção:
- Montagem: 4-6 horas
- Programação: 2-3 horas
- Instalação: 2-4 horas
- Total: 8-13 horas (fim de semana)
Lista de Materiais
Componentes Eletrônicos
Microcontrolador:
- Arduino Nano: R$ 25-35 (ou ESP32: R$ 40-60)
- Protoboard: R$ 15-25
- Jumpers: R$ 10-15
Atuador:
- Servo Motor SG90: R$ 20-30 (para portas leves)
- Servo MG996R: R$ 40-60 (para portas pesadas)
- Micro servo DS04-NFC: R$ 30-45 (alternativa compacta)
Conectividade:
- Módulo ESP8266: R$ 25-40 (se usar Arduino Nano)
- Antena Wi-Fi: R$ 10-15 (opcional para melhor sinal)
Alimentação:
- Fonte 5V 2A: R$ 20-30
- Bateria 9V: R$ 15-25 (backup)
- Conector P4: R$ 5-10
Sensores e Interface:
- LED RGB: R$ 3-5
- Buzzer: R$ 5-8
- Botão push: R$ 2-5
- Resistores: R$ 5-10 (kit)
Proteção:
- Caixa plástica: R$ 15-30
- Relé 5V: R$ 8-15 (se usar fechadura elétrica)
Ferramentas Necessárias
Básicas:
- Furadeira com brocas variadas
- Chaves de fenda e philips
- Alicate descascador
- Multímetro básico
- Cola quente ou epóxi
Opcionais:
- Ferro de solda + solda
- Sugador de solda
- Morsa pequena
- Dremel (para ajustes finos)
Materiais de Instalação
Mecânicos:
- Suporte servo: Impresso 3D ou adaptado
- Acoplamento: Conectar servo à fechadura
- Parafusos M3: Fixação do sistema
- Abraçadeiras: Organização de cabos
Custo Total Estimado:
- Básico: R$ 150-250
- Completo: R$ 250-400
- Premium: R$ 400-600 (com ESP32, sensores extras)
Esquema Eletrônico
Conexões Arduino Nano + ESP8266
Arduino Nano:
- D2 → Servo Motor (sinal)
- D3 → LED RGB (vermelho)
- D4 → LED RGB (verde)
- D5 → LED RGB (azul)
- D6 → Buzzer
- D7 → Botão manual
- D8 → ESP8266 (TX)
- D9 → ESP8266 (RX)
- 5V → VCC componentes
- GND → GND comum
ESP8266:
- TX → Arduino D9
- RX → Arduino D8
- VCC → 3.3V (divisor de tensão)
- GND → GND comum
Alternativa com ESP32 (Recomendada)
ESP32:
- GPIO 18 → Servo Motor
- GPIO 19 → LED RGB (R)
- GPIO 21 → LED RGB (G)
- GPIO 22 → LED RGB (B)
- GPIO 23 → Buzzer
- GPIO 25 → Botão manual
- 5V → Servo VCC
- 3.3V → ESP32 VCC
- GND → GND comum
Diagrama de Alimentação
Fonte 5V 2A:
├── Arduino/ESP32 (via regulador)
├── Servo Motor (direto 5V)
├── LEDs (com resistores)
└── Backup Bateria 9V
Código Arduino Completo
Código para ESP32 (Versão Completa)
#include <WiFi.h>
#include <WebServer.h>
#include <ESP32Servo.h>
#include <EEPROM.h>
#include <ArduinoJson.h>
// Configurações Wi-Fi
const char* ssid = "SUA_REDE_WIFI";
const char* password = "SUA_SENHA_WIFI";
// Pinos
#define SERVO_PIN 18
#define LED_R 19
#define LED_G 21
#define LED_B 22
#define BUZZER 23
#define BUTTON 25
// Objetos
Servo fechadura;
WebServer server(80);
// Variáveis
bool portaAberta = false;
unsigned long ultimoAcesso = 0;
String logAcessos = "";
// Posições do servo
const int POSICAO_FECHADA = 0;
const int POSICAO_ABERTA = 90;
void setup() {
Serial.begin(115200);
// Configurar pinos
pinMode(LED_R, OUTPUT);
pinMode(LED_G, OUTPUT);
pinMode(LED_B, OUTPUT);
pinMode(BUZZER, OUTPUT);
pinMode(BUTTON, INPUT_PULLUP);
// Inicializar servo
fechadura.attach(SERVO_PIN);
fechadura.write(POSICAO_FECHADA);
// Conectar Wi-Fi
WiFi.begin(ssid, password);
setLED(255, 255, 0); // Amarelo = conectando
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Conectando WiFi...");
}
Serial.println("WiFi conectado!");
Serial.print("IP: ");
Serial.println(WiFi.localIP());
// Configurar servidor web
configurarServidor();
server.begin();
setLED(0, 255, 0); // Verde = conectado
bip(2); // Dois bips = sistema pronto
}
void loop() {
server.handleClient();
// Verificar botão manual
if (digitalRead(BUTTON) == LOW) {
delay(50); // Debounce
if (digitalRead(BUTTON) == LOW) {
togglePorta("manual");
while (digitalRead(BUTTON) == LOW) delay(10);
}
}
// Auto-fechar após 10 segundos
if (portaAberta && (millis() - ultimoAcesso > 10000)) {
fecharPorta("auto");
}
}
void configurarServidor() {
// Página principal
server.on("/", HTTP_GET, []() {
String html = gerarPaginaHTML();
server.send(200, "text/html", html);
});
// API - Abrir porta
server.on("/abrir", HTTP_POST, []() {
String codigo = server.arg("codigo");
if (validarCodigo(codigo)) {
abrirPorta("app");
server.send(200, "application/json", "{\"status\":\"sucesso\"}");
} else {
server.send(401, "application/json", "{\"status\":\"codigo_invalido\"}");
}
});
// API - Fechar porta
server.on("/fechar", HTTP_POST, []() {
fecharPorta("app");
server.send(200, "application/json", "{\"status\":\"sucesso\"}");
});
// API - Status
server.on("/status", HTTP_GET, []() {
String json = "{";
json += "\"porta_aberta\":" + String(portaAberta ? "true" : "false") + ",";
json += "\"ultimo_acesso\":" + String(ultimoAcesso) + ",";
json += "\"wifi_signal\":" + String(WiFi.RSSI());
json += "}";
server.send(200, "application/json", json);
});
// API - Histórico
server.on("/historico", HTTP_GET, []() {
server.send(200, "application/json", logAcessos);
});
}
String gerarPaginaHTML() {
String html = R"(
<!DOCTYPE html>
<html>
<head>
<title>Fechadura Inteligente DIY</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body { font-family: Arial; margin: 20px; background: #f0f0f0; }
.container { max-width: 400px; margin: 0 auto; background: white; padding: 20px; border-radius: 10px; }
.status { text-align: center; font-size: 24px; margin: 20px 0; }
.aberta { color: #27ae60; }
.fechada { color: #e74c3c; }
button { width: 100%; padding: 15px; margin: 10px 0; font-size: 18px; border: none; border-radius: 5px; cursor: pointer; }
.btn-abrir { background: #27ae60; color: white; }
.btn-fechar { background: #e74c3c; color: white; }
.btn-refresh { background: #3498db; color: white; }
.codigo { width: 100%; padding: 10px; margin: 10px 0; font-size: 16px; }
.info { background: #ecf0f1; padding: 10px; border-radius: 5px; margin: 10px 0; }
</style>
</head>
<body>
<div class="container">
<h1>🔒 Fechadura DIY</h1>
<div class="status" id="status">Carregando...</div>
<input type="password" class="codigo" id="codigo" placeholder="Código de acesso" maxlength="6">
<button class="btn-abrir" onclick="abrirPorta()">🔓 Abrir Porta</button>
<button class="btn-fechar" onclick="fecharPorta()">🔒 Fechar Porta</button>
<button class="btn-refresh" onclick="atualizarStatus()">🔄 Atualizar</button>
<div class="info">
<strong>Status Wi-Fi:</strong> <span id="wifi">-</span> dBm<br>
<strong>Último acesso:</strong> <span id="ultimo">-</span>
</div>
</div>
<script>
function atualizarStatus() {
fetch('/status')
.then(response => response.json())
.then(data => {
const status = document.getElementById('status');
if (data.porta_aberta) {
status.innerHTML = '🔓 PORTA ABERTA';
status.className = 'status aberta';
} else {
status.innerHTML = '🔒 PORTA FECHADA';
status.className = 'status fechada';
}
document.getElementById('wifi').textContent = data.wifi_signal;
document.getElementById('ultimo').textContent = new Date(data.ultimo_acesso).toLocaleString();
});
}
function abrirPorta() {
const codigo = document.getElementById('codigo').value;
fetch('/abrir', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'codigo=' + codigo
})
.then(response => response.json())
.then(data => {
if (data.status === 'sucesso') {
alert('✅ Porta aberta!');
document.getElementById('codigo').value = '';
atualizarStatus();
} else {
alert('❌ Código inválido!');
}
});
}
function fecharPorta() {
fetch('/fechar', {method: 'POST'})
.then(response => response.json())
.then(data => {
alert('✅ Porta fechada!');
atualizarStatus();
});
}
// Atualizar status a cada 5 segundos
setInterval(atualizarStatus, 5000);
atualizarStatus(); // Primeira execução
</script>
</body>
</html>
)";
return html;
}
bool validarCodigo(String codigo) {
// Códigos válidos (pode expandir para EEPROM)
return (codigo == "123456" || codigo == "admin" || codigo == "1234");
}
void abrirPorta(String origem) {
fechadura.write(POSICAO_ABERTA);
portaAberta = true;
ultimoAcesso = millis();
setLED(0, 255, 0); // Verde
bip(1); // Um bip
adicionarLog("ABRIR", origem);
Serial.println("Porta aberta por: " + origem);
}
void fecharPorta(String origem) {
fechadura.write(POSICAO_FECHADA);
portaAberta = false;
setLED(255, 0, 0); // Vermelho
bip(2); // Dois bips
adicionarLog("FECHAR", origem);
Serial.println("Porta fechada por: " + origem);
}
void togglePorta(String origem) {
if (portaAberta) {
fecharPorta(origem);
} else {
abrirPorta(origem);
}
}
void setLED(int r, int g, int b) {
analogWrite(LED_R, r);
analogWrite(LED_G, g);
analogWrite(LED_B, b);
}
void bip(int vezes) {
for (int i = 0; i < vezes; i++) {
digitalWrite(BUZZER, HIGH);
delay(100);
digitalWrite(BUZZER, LOW);
delay(100);
}
}
void adicionarLog(String acao, String origem) {
String timestamp = String(millis());
String novoLog = "{\"timestamp\":" + timestamp + ",\"acao\":\"" + acao + "\",\"origem\":\"" + origem + "\"}";
if (logAcessos.length() > 0) {
logAcessos = novoLog + "," + logAcessos;
} else {
logAcessos = novoLog;
}
// Manter apenas últimos 50 registros
int virgulas = 0;
for (int i = 0; i < logAcessos.length(); i++) {
if (logAcessos.charAt(i) == ',') {
virgulas++;
if (virgulas >= 50) {
logAcessos = logAcessos.substring(0, i);
break;
}
}
}
}
Montagem Passo a Passo
Passo 1: Preparação da Eletrônica
Montagem na Protoboard:
- Posicione o ESP32 no centro da protoboard
- Conecte as alimentações:
- Linha vermelha: 5V
- Linha azul: GND
- Linha amarela: 3.3V
- Teste as conexões com multímetro
Conexão do Servo:
Servo SG90:
├── Fio vermelho → 5V
├── Fio marrom → GND
└── Fio laranja → GPIO 18
LEDs e Buzzer:
LED RGB (ânodo comum):
├── Ânodo → 3.3V
├── R → GPIO 19 (via resistor 220Ω)
├── G → GPIO 21 (via resistor 220Ω)
└── B → GPIO 22 (via resistor 220Ω)
Buzzer:
├── + → GPIO 23
└── - → GND
Passo 2: Teste da Eletrônica
Upload do Código:
- Instale Arduino IDE 2.0
- Adicione biblioteca ESP32
- Instale bibliotecas necessárias
- Configure sua rede Wi-Fi no código
- Faça upload para o ESP32
Testes Funcionais:
// Código de teste básico
void testarComponentes() {
// Teste servo
fechadura.write(0);
delay(1000);
fechadura.write(90);
delay(1000);
// Teste LEDs
setLED(255, 0, 0); delay(500);
setLED(0, 255, 0); delay(500);
setLED(0, 0, 255); delay(500);
// Teste buzzer
bip(3);
}
Passo 3: Adaptação Mecânica
Análise da Fechadura Existente:
- Meça o curso necessário do mecanismo
- Identifique ponto de aplicação de força
- Verifique espaço disponível para instalação
- Teste resistência mecânica
Criação do Acoplamento:
Opções de acoplamento:
1. Impressão 3D (ideal)
2. Adaptação com perfil de alumínio
3. Soldagem de peças metálicas
4. Uso de abraçadeiras e parafusos
Dimensões Típicas:
- Curso do servo: 0° a 90° (aprox. 2cm linear)
- Força disponível: 1.5-3kg/cm
- Velocidade: 0.1s/60°
Passo 4: Instalação Física
Preparação do Local:
- Desligue energia da fechadura elétrica (se houver)
- Remova painéis de acesso
- Meça e marque posições de fixação
- Teste mecanismo manualmente
Fixação do Sistema:
Checklist de instalação:
□ Servo fixado com parafusos M3
□ Acoplamento alinhado corretamente
□ Movimento livre sem travamentos
□ Cabos organizados e protegidos
□ Caixa de proteção instalada
□ Fonte de alimentação conectada
Teste de Integração:
- Movimento manual suave
- Acionamento eletrônico preciso
- Retorno à posição inicial
- Funcionamento da chave manual
Configuração do Aplicativo
Interface Web Responsiva
O código inclui uma interface web completa que funciona como aplicativo:
Recursos da Interface:
- Design responsivo: Funciona em qualquer dispositivo
- Status em tempo real: Atualização automática
- Controles intuitivos: Botões grandes e claros
- Histórico de acesso: Log completo de operações
- Indicadores visuais: Status da porta e Wi-Fi
Criando Atalho no Smartphone
Android:
- Abra Chrome e acesse o IP da fechadura
- Menu → “Adicionar à tela inicial”
- Nomeie como “Fechadura Casa”
- Ícone aparecerá como app nativo
iOS:
- Abra Safari e acesse o IP da fechadura
- Botão compartilhar → “Adicionar à Tela de Início”
- Confirme o nome e ícone
- App disponível na tela principal
Configuração de Códigos
Códigos Padrão:
123456– Código famíliaadmin– Código administrador1234– Código emergência
Adicionar Novos Códigos:
bool validarCodigo(String codigo) {
String codigos[] = {"123456", "admin", "1234", "visitante"};
int totalCodigos = 4;
for (int i = 0; i < totalCodigos; i++) {
if (codigo == codigos[i]) {
return true;
}
}
return false;
}
Recursos Avançados
Integração com Assistentes de Voz
Alexa/Google Assistant via IFTTT:
// Webhook para IFTTT
server.on("/ifttt", HTTP_POST, []() {
String key = server.arg("key");
String action = server.arg("action");
if (key == "SUA_CHAVE_IFTTT") {
if (action == "abrir") {
abrirPorta("alexa");
} else if (action == "fechar") {
fecharPorta("alexa");
}
server.send(200, "text/plain", "OK");
} else {
server.send(401, "text/plain", "Unauthorized");
}
});
Sistema de Códigos Temporários
struct CodigoTemporario {
String codigo;
unsigned long expiracao;
String nome;
};
CodigoTemporario codigosTemp[10];
int totalCodigosTemp = 0;
void adicionarCodigoTemporario(String codigo, unsigned long duracao, String nome) {
if (totalCodigosTemp < 10) {
codigosTemp[totalCodigosTemp].codigo = codigo;
codigosTemp[totalCodigosTemp].expiracao = millis() + duracao;
codigosTemp[totalCodigosTemp].nome = nome;
totalCodigosTemp++;
}
}
bool validarCodigoTemporario(String codigo) {
unsigned long agora = millis();
for (int i = 0; i < totalCodigosTemp; i++) {
if (codigosTemp[i].codigo == codigo) {
if (agora < codigosTemp[i].expiracao) {
return true;
} else {
// Remove código expirado
for (int j = i; j < totalCodigosTemp - 1; j++) {
codigosTemp[j] = codigosTemp[j + 1];
}
totalCodigosTemp--;
return false;
}
}
}
return false;
}
Notificações Push
Integração com Pushover:
#include <HTTPClient.h>
void enviarNotificacao(String mensagem) {
HTTPClient http;
http.begin("https://api.pushover.net/1/messages.json");
http.addHeader("Content-Type", "application/x-www-form-urlencoded");
String dados = "token=SEU_TOKEN&user=SEU_USER&message=" + mensagem;
int httpCode = http.POST(dados);
if (httpCode > 0) {
Serial.println("Notificação enviada");
}
http.end();
}
void abrirPorta(String origem) {
fechadura.write(POSICAO_ABERTA);
portaAberta = true;
ultimoAcesso = millis();
setLED(0, 255, 0);
bip(1);
adicionarLog("ABRIR", origem);
enviarNotificacao("🔓 Porta aberta por: " + origem);
}
Solução de Problemas
Problemas Comuns
Servo Não Funciona:
- Causa: Alimentação insuficiente
- Solução: Use fonte 5V 2A dedicada
- Teste: Meça tensão nos pinos do servo
Wi-Fi Não Conecta:
- Causa: SSID/senha incorretos ou sinal fraco
- Solução: Verifique credenciais e proximidade do roteador
- Debug: Monitor serial mostra detalhes da conexão
Página Web Não Carrega:
- Causa: IP incorreto ou firewall
- Solução: Verifique IP no monitor serial
- Teste: Ping para o dispositivo
Movimento Irregular:
- Causa: Acoplamento desalinhado ou atrito
- Solução: Ajuste mecânico e lubrificação
- Prevenção: Teste regular de movimento
Diagnóstico Avançado
Código de Diagnóstico:
void diagnosticoCompleto() {
Serial.println("=== DIAGNÓSTICO FECHADURA DIY ===");
// Teste Wi-Fi
Serial.print("Wi-Fi Status: ");
Serial.println(WiFi.status() == WL_CONNECTED ? "Conectado" : "Desconectado");
Serial.print("IP Local: ");
Serial.println(WiFi.localIP());
Serial.print("Sinal: ");
Serial.print(WiFi.RSSI());
Serial.println(" dBm");
// Teste Servo
Serial.println("Testando servo...");
fechadura.write(0);
delay(1000);
fechadura.write(90);
delay(1000);
fechadura.write(45);
// Teste LEDs
Serial.println("Testando LEDs...");
setLED(255, 0, 0); delay(300);
setLED(0, 255, 0); delay(300);
setLED(0, 0, 255); delay(300);
setLED(0, 0, 0);
// Teste Buzzer
Serial.println("Testando buzzer...");
bip(3);
// Teste Botão
Serial.println("Teste do botão - pressione para confirmar");
while (digitalRead(BUTTON) == HIGH) {
delay(10);
}
Serial.println("Botão OK!");
Serial.println("=== DIAGNÓSTICO CONCLUÍDO ===");
}
Melhorias e Expansões
Versão 2.0 – Recursos Avançados
Leitor RFID:
#include <MFRC522.h>
#define SS_PIN 5
#define RST_PIN 27
MFRC522 rfid(SS_PIN, RST_PIN);
void setup() {
// ... código existente ...
SPI.begin();
rfid.PCD_Init();
}
void loop() {
// ... código existente ...
if (rfid.PICC_IsNewCardPresent() && rfid.PICC_ReadCardSerial()) {
String uid = "";
for (byte i = 0; i < rfid.uid.size; i++) {
uid += String(rfid.uid.uidByte[i], HEX);
}
if (validarRFID(uid)) {
abrirPorta("rfid");
}
rfid.PICC_HaltA();
}
}
Sensor de Porta:
#define SENSOR_PORTA 26
void setup() {
// ... código existente ...
pinMode(SENSOR_PORTA, INPUT_PULLUP);
}
void loop() {
// ... código existente ...
bool portaFisicaAberta = digitalRead(SENSOR_PORTA) == HIGH;
if (portaFisicaAberta && !portaAberta) {
// Porta foi aberta fisicamente
enviarNotificacao("⚠️ Porta aberta sem comando!");
adicionarLog("ABERTA_FISICA", "sensor");
}
}
Integração com Home Assistant
Configuração YAML:
switch:
- platform: rest
name: "Fechadura Casa"
resource: "http://IP_DA_FECHADURA/toggle"
method: POST
body_on: '{"action": "abrir"}'
body_off: '{"action": "fechar"}'
headers:
Content-Type: application/json
binary_sensor:
- platform: rest
name: "Status Fechadura"
resource: "http://IP_DA_FECHADURA/status"
method: GET
value_template: "{{ value_json.porta_aberta }}"
Aplicativo Mobile Nativo
Estrutura React Native Básica:
import React, { useState, useEffect } from 'react';
import { View, Text, TouchableOpacity, Alert } from 'react-native';
const FechaduraApp = () => {
const [status, setStatus] = useState(false);
const [loading, setLoading] = useState(false);
const IP_FECHADURA = '192.168.1.100'; // Seu IP aqui
const abrir
