This project automates a retractable roof system using an ESP32 microcontroller to manage a 12V DC motor through a high-current H-bridge driver. The system integrates rain detection, limit switches, and manual controls to safely open and close the roof while preventing damage from weather or mechanical faults.
The guide provides a complete wiring diagram, detailed parts list, and step-by-step assembly instructions that separate the power circuit from the logic layer using a level shifter for safe ESP32 integration. Firmware with WiFi and Bluetooth control is included, along with safety features like debounced inputs, travel time limits, and dry-weather tracking. Testing procedures ensure reliable operation before final installation.
Wiring diagram
Gather all the parts
Assemble it in 6 steps
1. Separe a montagem da carga real
Primeiro teste com o motor desacoplado do telhado. Este exemplo usa motor de 12 V ate 2 A nominal e 5 A de partida; dimensione torque, cabos, fusivel e fonte para o motor real.
- Nao e um controlador de seguranca certificado. Um telhado pode esmagar pessoas; exige protecao independente contra obstaculos e queda, alem de parada de emergencia.
- Nao use protoboard nos fios de potencia. A marcacao 43 A nao significa corrente continua segura do modulo.
2. Monte o circuito de potencia desligado
Positivo da fonte em fusivel, depois COM da emergencia; NC da emergencia em B+ da ponte. Negativo da fonte e B- no GND comum. M+ e M- nos dois fios do motor. Capacitor de 470 uF junto a B+ e B-, positivo em B+. Use fonte isolada pronta, sem mexer na rede eletrica.
3. Ligue a logica com conversao de nivel
ESP32 alimentado por USB; pino 5V alimenta VCC da ponte e do 74AHCT125. Nunca conecte 12 V ao ESP32. Una os GND. No DIP-14: 1OE=1, 1A=2, 1Y=3, 2OE=4, 2A=5, 2Y=6, GND=7, 3Y=8, 3A=9, 3OE=10, 4Y=11, 4A=12, 4OE=13, VCC=14. GPIO25 em 1A, GPIO26 em 2A, GPIO27 em 3A. Saidas 1Y em RPWM, 2Y em LPWM, 3Y nos dois EN. OE usados no GND; 4OE em 5V e 4A no GND; 4Y sem fio. Cada entrada usada recebe resistor de 10 kohm para GND. Capacitor 100 nF entre VCC e GND do CI.
4. Instale chuva e comandos
Chuva VCC em 3V3, GND no GND e DO no GPIO32. Ajuste o potenciometro para DO ficar baixo quando molhado. Botoes ABRIR, FECHAR e PARAR ligam respectivamente GPIO21, GPIO22 e GPIO23 ao GND ao pressionar. Sao contatos sem tensao externa.
- Proteja a placa comparadora da agua; apenas a placa sensora fica exposta. Inspecione corrosao periodicamente.
5. Monte os fins de curso normalmente fechados
COM de cada fim no GND. NC do fim ABERTO em GPIO18; NC do fim FECHADO em GPIO19. NO fica sem fio. Em repouso NC conduz; no limite abre o circuito. Um cabo rompido tambem indica limite. Fixe-os antes dos batentes mecanicos.
- Os fins sao lidos pelo programa; nao substituem intertravamento de seguranca independente. Fio em curto nao e detectado.
6. Teste sem carga antes de instalar
Com motor desacoplado, verifique sentido: ABRIR deve abrir; se invertido, desligue tudo e troque os dois fios do motor. Acione cada fim e confirme parada. Molhe o sensor e confirme fechamento; depois seque e aguarde 30 minutos. Teste PARAR, corte de emergencia e falha de fim. Rearme a emergencia apenas apos pressionar PARAR e verificar a area livre.
- Rearmar a emergencia sozinho pode permitir retorno automatico do movimento; use PARAR para bloquear antes do rearme.
- Sem sensor de obstaculo, a limitacao de tempo nao impede esmagamento. Nao liberar uso real sem sistema de seguranca independente.
Review all connections
1. Connections between "fonte" and "ESP32"
2. Connections between "fusivel" and "ESP32"
3. Connections between "emergencia" and "ESP32"
4. Connections between "ponte" and "ESP32"
5. Connections between "c2" and "ESP32"
6. Connections between "chuva" and "ESP32"
7. Connections between "nivel" and "ESP32"
8. Connections between "r1" and "ESP32"
9. Connections between "r2" and "ESP32"
10. Connections between "r3" and "ESP32"
11. Connections between "c1" and "ESP32"
12. Connections between "fim_aberto" and "ESP32"
13. Connections between "fim_fechado" and "ESP32"
14. Connections between "abrir" and "ESP32"
15. Connections between "fechar" and "ESP32"
16. Connections between "parar" and "ESP32"
Deploy the firmware
#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
#include <BluetoothSerial.h>
// Edite as credenciais antes de usar Deploy. Sem credenciais, cria rede local.
const char* WIFI_SSID = "";
const char* WIFI_PASSWORD = "";
const char* AP_PASSWORD = "Telhado-2026!"; // Troque por senha propria.
constexpr int RPWM=25, LPWM=26, ENABLE=27;
constexpr int RAIN=32, LIMIT_OPEN=18, LIMIT_CLOSED=19;
constexpr int BUTTON_OPEN=21, BUTTON_CLOSE=22, BUTTON_STOP=23;
constexpr uint32_t DRY_WAIT=30UL*60UL*1000UL;
constexpr uint32_t MAX_TRAVEL=60000; // Ajustar ao percurso medido, com margem pequena.
constexpr uint32_t REVERSE_PAUSE=500;
WebServer server(80);
BluetoothSerial bt;
struct Debounced {
int pin; bool value, raw; uint32_t changed;
void begin() { pinMode(pin,INPUT_PULLUP); value=raw=digitalRead(pin); changed=millis(); }
bool update(uint32_t now, uint32_t ms=40) {
bool r=digitalRead(pin);
if(r!=raw) { raw=r; changed=now; }
if(value!=raw && uint32_t(now-changed)>=ms) { value=raw; return true; }
return false;
}
};
Debounced rain{RAIN}, bo{BUTTON_OPEN}, bc{BUTTON_CLOSE}, bs{BUTTON_STOP};
int moving=0, pending=0; // +1 abrir, -1 fechar
uint32_t stoppedAt=0, travelAt=0, dryAt=0, retryAt=0;
bool hold=false, fault=false, dryTracking=false, rainNotified=false;
String event="Inicializando";
void notify(const String &s) { event=s; Serial.println(s); bt.println(s); }
void stopMotor() {
digitalWrite(ENABLE,LOW); digitalWrite(RPWM,LOW); digitalWrite(LPWM,LOW);
if(moving) stoppedAt=millis();
moving=0;
}
bool opened() { return digitalRead(LIMIT_OPEN)==HIGH; } // NC: aberto ao atingir fim ou romper fio
bool closed() { return digitalRead(LIMIT_CLOSED)==HIGH; }
void request(int direction) {
if(fault || (direction>0 && !rain.value)) return;
if((direction>0 && opened()) || (direction<0 && closed())) return;
if(moving==direction || pending==direction) return;
stopMotor(); pending=direction;
}
String statusText() {
return String("Chuva: ")+(!rain.value?"sim":"nao")+" | Motor: "+
(moving>0?"abrindo":moving<0?"fechando":"parado")+
" | Fim aberto: "+(opened()?"sim":"nao")+" | Fim fechado: "+(closed()?"sim":"nao")+
" | Bloqueado: "+(hold?"sim":"nao")+" | Falha: "+(fault?"sim":"nao")+"\n"+event;
}
const char PAGE[] PROGMEM=R"html(<!doctype html><html lang="pt-BR"><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>Telhado</title><h1>Telhado retrátil</h1><pre id="s">Conectando...</pre><p>Mantenha esta página aberta para acompanhar os avisos. Controle nos botões físicos.</p><script>async function update(){try{let r=await fetch('/status',{cache:'no-store'});document.getElementById('s').textContent=await r.text()}catch(e){document.getElementById('s').textContent='Sem comunicação'}}setInterval(update,1000);update();</script></html>)html";
void setup() {
digitalWrite(ENABLE,LOW); digitalWrite(RPWM,LOW); digitalWrite(LPWM,LOW);
pinMode(ENABLE,OUTPUT); pinMode(RPWM,OUTPUT); pinMode(LPWM,OUTPUT);
pinMode(LIMIT_OPEN,INPUT_PULLUP); pinMode(LIMIT_CLOSED,INPUT_PULLUP);
rain.begin(); bo.begin(); bc.begin(); bs.begin();
Serial.begin(115200); bt.begin("Telhado-ESP32"); // SPP: terminal Bluetooth compativel, tipicamente Android.
stoppedAt=millis();
if(strlen(WIFI_SSID)) { WiFi.mode(WIFI_STA); WiFi.begin(WIFI_SSID,WIFI_PASSWORD); }
else { WiFi.mode(WIFI_AP); WiFi.softAP("Telhado-ESP32",AP_PASSWORD); Serial.println(WiFi.softAPIP()); }
server.on("/", [](){server.send_P(200,"text/html; charset=utf-8",PAGE);});
server.on("/status", [](){server.send(200,"text/plain; charset=utf-8",statusText());});
server.begin(); notify("Sistema iniciado; verificando chuva e fins de curso");
}
void loop() {
uint32_t now=millis();
server.handleClient();
rain.update(now,200); bool op=bo.update(now), cl=bc.update(now); bs.update(now);
// STOP tem prioridade e fica travado. Soltar nao reinicia movimento.
if(!bs.raw) {
pending=0; stopMotor();
if(!hold) notify("Parada manual: automatico suspenso; pressione abrir ou fechar para rearmar");
hold=true;
}
if(opened() && closed()) {
pending=0; stopMotor();
if(!fault) notify("Falha: dois fins ativos ou fios interrompidos; verificar circuito");
fault=true;
}
if(moving && uint32_t(now-travelAt)>=MAX_TRAVEL) {
pending=0; stopMotor(); fault=true; notify("Falha: tempo maximo de movimento; verificar mecanismo antes de rearmar");
}
// Limites param imediatamente, sem esperar debounce.
if((moving>0 && opened()) || (moving<0 && closed())) {
int previous=moving; stopMotor(); pending=0;
if(previous<0 && !rain.value) { notify("Tempo chuvoso, telhado fechado"); rainNotified=true; }
else notify(previous>0?"Telhado aberto":"Telhado fechado");
}
if(bs.raw && !(opened() && closed())) {
if(cl && !bc.value) { fault=false; hold=false; request(-1); }
else if(op && !bo.value) {
if(!rain.value) notify("Abertura bloqueada pela chuva");
else { fault=false; hold=false; request(1); }
}
}
if(!rain.value) {
dryTracking=false;
// Cancela abertura mesmo se a parada manual estiver travada.
if(moving>0 || pending>0) { pending=0; stopMotor(); }
if(!hold && !fault) {
request(-1);
if(closed() && !rainNotified) { notify("Tempo chuvoso, telhado fechado"); rainNotified=true; }
}
} else {
rainNotified=false;
if(!dryTracking) { dryTracking=true; dryAt=now; }
if(!hold && !fault && uint32_t(now-dryAt)>=DRY_WAIT) request(1);
}
if(pending && !hold && !fault && bs.raw && uint32_t(now-stoppedAt)>=REVERSE_PAUSE) {
int d=pending; pending=0;
if(!((d>0 && (opened() || !rain.value)) || (d<0 && closed()))) {
digitalWrite(RPWM,d>0?HIGH:LOW); digitalWrite(LPWM,d<0?HIGH:LOW);
digitalWrite(ENABLE,HIGH); moving=d; travelAt=now;
notify(d>0?"Tempo ensolarado, abrindo telhado":"Chuva ou comando manual: fechando telhado");
}
}
// Wi-Fi nunca bloqueia a rotina de seguranca.
static bool connected=false;
if(strlen(WIFI_SSID)) {
bool c=WiFi.status()==WL_CONNECTED;
if(c && !connected) { Serial.print("Pagina do telhado: http://"); Serial.println(WiFi.localIP()); }
connected=c;
if(!c && uint32_t(now-retryAt)>=15000) { retryAt=now; WiFi.reconnect(); }
}
// Bluetooth somente leitura: qualquer caractere consulta o estado.
if(bt.available()) { while(bt.available()) bt.read(); bt.println(statusText()); }
}Remix this project
Make it yours in one click
Open a full copy of this project in your own Schematik workspace, diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.




