Community project

Smart Retractable Roof Control

Photo of Smart Retractable Roof Control
Generated with AI

Cirly Rodrigues

Published November 5, 2026

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

Loading diagram…

Gather all the parts

QtyComponent
1

IBT-2 BTS7960 43A High-Current H-Bridge Motor Driver

IBT-2 high-current H-bridge motor driver module built around BTS7960 half-bridge devices. Common modules are sold for 6V-27V motor supplies and 5V logic, with 43A as a module headline rating; practical continuous current depends on board cooling and wiring. Direction and speed are controlled with RPWM/LPWM PWM inputs plus R_EN/L_EN enable lines. Logic-side VCC is separate from the high-power motor supply, and grounds must be common.

1

Rain Sensor Module

A rain sensor module consisting of a sensing pad with exposed copper traces and an LM393 comparator circuit. When water bridges the copper traces on the sensing pad, resistance decreases and conductivity increases. The module provides both an analog output (AO) proportional to water level and a digital output (DO) that goes LOW when water exceeds a threshold set by an onboard potentiometer. Includes power and status LEDs. Designed for detecting rain, slush, or hail in weather-responsive automation projects.

1

74AHCT125 Quad Buffer / Level Shifter

Quad non-inverting buffer with 3-state outputs and active-low output enables, in a breadboard-friendly 14-pin DIP. Powered from 5 V, its TTL-level inputs accept 3.3 V logic, so it is a common one-way 3.3 V to 5 V level shifter for NeoPixel/WS2812 data and other fast digital signals; each output drives up to 8 mA.

1

Microinterruptor Zippy COM NC NO

Contato de fim de curso normalmente fechado; NO sem uso.

1

Microinterruptor Zippy COM NC NO

Contato de fim de curso normalmente fechado; NO sem uso.

1

Push Button

Momentary push button switch

1

Push Button

Momentary push button switch

1

Push Button

Momentary push button switch

1

Motorredutor DC 12 V

Motor de dois fios; exemplo limitado a 2 A nominal e 5 A de partida. Dimensionar torque para a carga.

1

Fonte isolada 12 V 5 A

Fonte externa pronta e certificada; nao abrir nem conectar rede eletrica ao circuito.

1

Fusivel 5 A com porta-fusivel

Protege a fiacao; ajustar para corrente real e bitola.

1

Botao cogumelo travante NC, 12 V DC 10 A

Corta fisicamente a alimentacao de potencia do motor; rearme manual.

1

Resistor 10 kohm

Mantem comando desligado durante inicializacao.

1

Resistor 10 kohm

Mantem comando desligado durante inicializacao.

1

Resistor 10 kohm

Mantem comando desligado durante inicializacao.

1

Capacitor ceramico 100 nF

Desacoplamento do 74AHCT125 junto aos pinos de alimentacao.

1

Capacitor eletrolitico 470 uF 25 V

Filtro junto a ponte H; respeitar polaridade.

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"

FunctionfonteESP32
power+ → Fusivel 5 A com porta-fusivel 1EXT
ground-GND

2. Connections between "fusivel" and "ESP32"

FunctionfusivelESP32
power2 → Botao cogumelo travante NC, 12 V DC 10 A COMEXT

3. Connections between "emergencia" and "ESP32"

FunctionemergenciaESP32
powerNC → IBT-2 BTS7960 43A High-Current H-Bridge Motor Driver B+EXT

4. Connections between "ponte" and "ESP32"

FunctionponteESP32
groundB-GND
groundGNDGND
powerVCC5V
dataM+ → Motorredutor DC 12 V AEXT
dataM- → Motorredutor DC 12 V BEXT

5. Connections between "c2" and "ESP32"

Functionc2ESP32
power+ → IBT-2 BTS7960 43A High-Current H-Bridge Motor Driver B+EXT
ground-GND

6. Connections between "chuva" and "ESP32"

FunctionchuvaESP32
powerVCC3V3
groundGNDGND
digitalDOGPIO 32

7. Connections between "nivel" and "ESP32"

FunctionnivelESP32
powerVCC5V
groundGNDGND
ground1OEGND
ground2OEGND
ground3OEGND
digital1AGPIO 25
digital2AGPIO 26
digital3AGPIO 27
digital1Y → IBT-2 BTS7960 43A High-Current H-Bridge Motor Driver RPWMEXT
digital2Y → IBT-2 BTS7960 43A High-Current H-Bridge Motor Driver LPWMEXT
digital3Y → IBT-2 BTS7960 43A High-Current H-Bridge Motor Driver R_ENEXT
digital3Y → IBT-2 BTS7960 43A High-Current H-Bridge Motor Driver L_ENEXT
power4OE5V
ground4AGND

8. Connections between "r1" and "ESP32"

Functionr1ESP32
data1 → 74AHCT125 Quad Buffer / Level Shifter 1AEXT
ground2GND

9. Connections between "r2" and "ESP32"

Functionr2ESP32
data1 → 74AHCT125 Quad Buffer / Level Shifter 2AEXT
ground2GND

10. Connections between "r3" and "ESP32"

Functionr3ESP32
data1 → 74AHCT125 Quad Buffer / Level Shifter 3AEXT
ground2GND

11. Connections between "c1" and "ESP32"

Functionc1ESP32
power15V
ground2GND

12. Connections between "fim_aberto" and "ESP32"

Functionfim_abertoESP32
groundCOMGND
digitalNCGPIO 18

13. Connections between "fim_fechado" and "ESP32"

Functionfim_fechadoESP32
groundCOMGND
digitalNCGPIO 19

14. Connections between "abrir" and "ESP32"

FunctionabrirESP32
digitalSIGNALGPIO 21
groundGNDGND

15. Connections between "fechar" and "ESP32"

FunctionfecharESP32
digitalSIGNALGPIO 22
groundGNDGND

16. Connections between "parar" and "ESP32"

FunctionpararESP32
digitalSIGNALGPIO 23
groundGNDGND

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.

Open in Schematik