Smart Room Controller with ESP32 and Firebase

An ESP32-powered smart room system that monitors light and temperature, controls LEDs and alerts, and controls an AC and projector.

IntermediateFull instructions provided10 hours21
Smart Room Controller with ESP32 and Firebase

Things used in this project

Hardware components

Espressif ESP32 DEVKIT_C
×1
Solderless Breadboard Full Size
Solderless Breadboard Full Size
×1
Infrared Transmitter
×1
Infrared Receiver, 38 kHz
Infrared Receiver, 38 kHz
×1
LDR, 5 Mohm
LDR, 5 Mohm
×1
Temperature Sensor
Temperature Sensor
×1
Buzzer
Buzzer
×1
5 mm LED: Red
5 mm LED: Red
×1
RGB Diffused Common Cathode
RGB Diffused Common Cathode
×1
Male/Female Jumper Wires
Male/Female Jumper Wires
×12
Jumper wires (generic)
Jumper wires (generic)
Male/Male
×12
Resistor 220 ohm
Resistor 220 ohm
×4
Resistor 10k ohm
Resistor 10k ohm
×2

Software apps and online services

PlatformIO IDE
PlatformIO IDE
Visual Studio Code Extension for Arduino
Microsoft Visual Studio Code Extension for Arduino
Firebase
Google Firebase
Components: Firebase Realtime Database; Firebase Hosting; Firebase Web SDK.
IRRemoteESP8266 library for PlatformIO VSCode Extension
ArduinoJson library
HTML, CSS, and JavaScript dashboard

Story

Read more

Schematics

Smart Room with ESP32 - Schematic

Too complex for Fritzing

Code

index.html

HTML
Frontend for the Firebase web app.
<!DOCTYPE html>
<html lang="pt-BR">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Ambiente Inteligente</title>
  <style>
    :root {
      color-scheme: dark;
      --bg: #0f1115;
      --panel: #181c24;
      --panel-2: #202633;
      --text: #f4f7fb;
      --muted: #a8b3c7;
      --line: #303848;
      --blue: #2ea8ff;
      --green: #33c481;
      --red: #ff5c73;
      --yellow: #ffd166;
      --orange: #ff9f1c;
    }

    * {
      box-sizing: border-box;
    }

    body {
      margin: 0;
      min-height: 100vh;
      background: var(--bg);
      color: var(--text);
      font-family: Arial, Helvetica, sans-serif;
    }

    main {
      width: min(980px, calc(100% - 32px));
      margin: 0 auto;
      padding: 28px 0;
    }

    header {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 16px;
      margin-bottom: 20px;
    }

    h1,
    h2,
    p {
      margin: 0;
    }

    h1 {
      font-size: clamp(1.6rem, 4vw, 2.4rem);
      font-weight: 800;
    }

    .status {
      min-width: 122px;
      padding: 8px 12px;
      border: 1px solid var(--line);
      border-radius: 999px;
      color: var(--muted);
      text-align: center;
      font-size: 0.9rem;
    }

    .status.online {
      color: var(--green);
      border-color: rgba(51, 196, 129, 0.45);
    }

    .status.warning {
      color: var(--orange);
      border-color: rgba(255, 159, 28, 0.5);
    }

    .status.error {
      color: var(--red);
      border-color: rgba(255, 92, 115, 0.5);
    }

    .grid {
      display: grid;
      grid-template-columns: repeat(3, minmax(0, 1fr));
      gap: 14px;
    }

    .card {
      min-height: 138px;
      padding: 18px;
      border: 1px solid var(--line);
      border-radius: 8px;
      background: var(--panel);
    }

    .card h2 {
      color: var(--muted);
      font-size: 0.95rem;
      font-weight: 700;
      letter-spacing: 0;
    }

    .value {
      margin-top: 14px;
      font-size: 2.2rem;
      font-weight: 800;
      line-height: 1.1;
    }

    .hint {
      margin-top: 8px;
      color: var(--muted);
      font-size: 0.92rem;
    }

    .message {
      margin-top: 14px;
      min-height: 22px;
      color: var(--muted);
      font-size: 0.95rem;
    }

    .message.ok {
      color: var(--green);
    }

    .message.error {
      color: var(--red);
    }

    .controls {
      display: grid;
      grid-template-columns: 1.3fr 1fr;
      gap: 14px;
      margin-top: 14px;
    }

    .row {
      display: flex;
      align-items: center;
      gap: 10px;
      flex-wrap: wrap;
      margin-top: 16px;
    }

    button {
      min-width: 112px;
      height: 42px;
      border: 1px solid transparent;
      border-radius: 8px;
      background: var(--panel-2);
      color: var(--text);
      font: inherit;
      font-weight: 700;
      cursor: pointer;
    }

    button:hover {
      border-color: var(--blue);
    }

    button:disabled {
      cursor: not-allowed;
      opacity: 0.55;
    }

    .primary {
      background: var(--blue);
      color: #06111d;
    }

    .danger {
      background: var(--red);
      color: #21030a;
    }

    .warning {
      background: var(--yellow);
      color: #1b1300;
    }

    input[type="color"] {
      width: 72px;
      height: 42px;
      padding: 2px;
      border: 1px solid var(--line);
      border-radius: 8px;
      background: var(--panel-2);
      cursor: pointer;
    }

    /* Estilos do Modal do Controle Remoto */
    .modal-overlay {
      position: fixed;
      top: 0; left: 0; right: 0; bottom: 0;
      background: rgba(0, 0, 0, 0.7);
      display: flex;
      justify-content: center;
      align-items: center;
      z-index: 1000;
      opacity: 0;
      pointer-events: none;
      transition: opacity 0.3s;
    }

    .modal-overlay.active {
      opacity: 1;
      pointer-events: auto;
    }

    .remote-modal {
      background: #181c24;
      border: 1px solid var(--line);
      border-radius: 20px;
      padding: 24px;
      width: 320px;
      box-shadow: 0 10px 30px rgba(0,0,0,0.5);
      position: relative;
    }

    .btn-close-modal {
      position: absolute;
      top: 10px; right: 10px;
      background: none;
      border: none;
      color: var(--muted);
      font-size: 1.5rem;
      cursor: pointer;
      min-width: auto;
      height: auto;
    }

    .remote-title {
      text-align: center;
      margin-bottom: 20px;
      color: var(--text);
    }

    /* Layout dos botes do controle */
    .remote-row {
      display: flex;
      justify-content: space-around;
      margin-bottom: 15px;
    }

    .remote-btn {
      width: 50px; height: 50px;
      min-width: 50px;
      border-radius: 50%;
      background: #e0e5ec;
      color: #333;
      font-weight: bold;
      display: flex;
      justify-content: center;
      align-items: center;
      font-size: 0.9rem;
    }
    
    .remote-btn:hover { border-color: var(--blue); }
    .remote-btn.power { background: var(--red); color: white; }
    
    /* D-Pad Direcional */
    .d-pad {
      display: grid;
      grid-template-areas: 
        ". up ."
        "left ok right"
        ". down .";
      gap: 5px;
      justify-content: center;
      margin: 20px 0;
    }
    
    .d-pad .remote-btn { background: #d0d7e0; }
    .d-pad .up { grid-area: up; }
    .d-pad .down { grid-area: down; }
    .d-pad .left { grid-area: left; }
    .d-pad .right { grid-area: right; }
    .d-pad .ok { 
      grid-area: ok; 
      background: #444 !important; 
      color: white; 
    }

    /* Botes Pill-shaped (Largos) */
    .remote-btn.pill {
      border-radius: 25px;
      width: 70px;
    }

    @media (max-width: 760px) {
      header,
      .controls {
        grid-template-columns: 1fr;
      }

      header {
        align-items: flex-start;
        flex-direction: column;
      }

      .grid {
        grid-template-columns: 1fr;
      }

      button {
        flex: 1 1 130px;
      }
    }
  </style>
</head>

<body>
  <main>
    <header>
      <div>
        <h1>Ambiente Inteligente</h1>
        <p class="hint">Dashboard IoT</p>
      </div>
      <div id="firebaseStatus" class="status">Conectando</div>
    </header>

    <section class="grid" aria-label="Leituras">
      <article class="card">
        <h2>Temperatura</h2>
        <p id="temp" class="value">--</p>
        <p class="hint">Limite: 35 C</p>
      </article>

      <article class="card">
        <h2>Luminosidade</h2>
        <p id="lum" class="value">--</p>
        <p class="hint">Limite: 2100</p>
      </article>

      <article class="card">
        <h2>LED principal</h2>
        <p id="ledStatus" class="value">--</p>
        <p id="modoStatus" class="hint">Modo: --</p>
      </article>
    </section>

    <section class="controls" aria-label="Controles">
      <article class="card">
        <h2>Controle do LED</h2>
        <div class="row">
          <button id="ledOn" class="primary" type="button">Ligar</button>
          <button id="ledOff" class="danger" type="button">Desligar</button>
          <button id="buzzer" class="warning" type="button">Testar buzzer</button>
        </div>
        <p class="hint">O modo manual volta ao automatico em 5 segundos.</p>
        <p id="commandStatus" class="message"></p>
      </article>

      <article class="card">
        <h2>LED RGB</h2>
        <div class="row">
          <input id="colorPicker" type="color" value="#000000" aria-label="Cor do LED RGB">
          <button id="applyColor" class="primary" type="button">Aplicar</button>
        </div>
        <p id="rgbStatus" class="hint">RGB: 0, 0, 0</p>
      </article>

      <article class="card">
        <h2>Controle IR (Sala)</h2>
        <div class="row">
          <button id="btnAbrirModalProjetor" class="primary" type="button">Controle do Projetor</button>
          <button id="btnAbrirModalAc" class="primary" type="button">Controle do Ar Condicionado</button>
        </div>
        <p class="hint">Dispara sinais infravermelhos gravados no ESP32.</p>
      </article>
    </section>
  </main>

  <!-- Modal do Controle Remoto -->
  <div class="modal-overlay" id="modalOverlay">
    <div class="remote-modal">
      <button class="btn-close-modal" id="btnFecharModal">&times;</button>
      <h2 class="remote-title">Projetor</h2>

      <div class="remote-row">
        <button class="remote-btn power" data-cmd="power"></button>
        <button class="remote-btn" data-cmd="mute"></button>
      </div>

      <div class="remote-row">
        <button class="remote-btn" data-cmd="play"></button>
        <button class="remote-btn pill" data-cmd="source">SRC</button>
        <button class="remote-btn pill" data-cmd="menu">MENU</button>
      </div>

      <div class="d-pad">
        <button class="remote-btn up" data-cmd="up"></button>
        <button class="remote-btn left" data-cmd="left"></button>
        <button class="remote-btn ok" data-cmd="ok">OK</button>
        <button class="remote-btn right" data-cmd="right"></button>
        <button class="remote-btn down" data-cmd="down"></button>
      </div>

      <div class="remote-row">
        <button class="remote-btn" data-cmd="rewind"></button>
        <button class="remote-btn" data-cmd="forward"></button>
        <button class="remote-btn" data-cmd="back"></button>
      </div>

      <div class="remote-row" style="background: #e0e5ec; border-radius: 25px; padding: 5px;">
        <button class="remote-btn pill" style="background:none;" data-cmd="prev"></button>
        <button class="remote-btn pill" style="background:none;" data-cmd="next"></button>
      </div>
    </div>
  </div>

  <div class="modal-overlay" id="modalOverlayAc">
    <div class="remote-modal" style="background: #e8ebd9; border: 1px solid #c2c5b6;">
      <button class="btn-close-modal" id="btnFecharModalAc" style="color: #555;">&times;</button>
      <h2 class="remote-title" style="color: #2b4970; font-weight: 800;">Carrier</h2>

      <div class="remote-row">
        <button class="remote-btn pill" id="btnAcLearn" type="button" style="background: #f4c95d; color: #332400;">Gravar</button>
      </div>

      <div class="remote-row" style="margin-top: 30px;">
        <button class="remote-btn pill" data-cmd-ac="fan" style="background: #a9b3a7; color: white;">Fan</button>
        <button class="remote-btn" data-cmd-ac="power" style="background: #7ca8d9; color: white; border-radius: 20px;"></button>
      </div>

      <div class="d-pad" style="background: #a9b3a7; border-radius: 50%; padding: 10px; width: 120px; margin: 20px auto;">
        <button class="remote-btn up" data-cmd-ac="tempUp" style="background: #e8ebd9; color: #555;"></button>
        <div style="grid-area: ok; display: flex; align-items: center; justify-content: center; color: white; font-weight: bold; font-size: 0.8rem;">TEMP</div>
        <button class="remote-btn down" data-cmd-ac="tempDown" style="background: #e8ebd9; color: #555;"></button>
      </div>

      <div class="remote-row">
        <button class="remote-btn pill" data-cmd-ac="sleep" style="background: #a9b3a7; color: white;">Sleep</button>
        <button class="remote-btn pill" data-cmd-ac="mode" style="background: #a9b3a7; color: white;">Mode</button>
      </div>
      
      <div class="remote-row">
        <button class="remote-btn pill" data-cmd-ac="sweep" style="background: #a9b3a7; color: white;">Sweep</button>
        <button class="remote-btn pill" data-cmd-ac="louver" style="background: #a9b3a7; color: white;">Louver</button>
      </div>
    </div>
  </div>

  <script type="module">
    import { initializeApp } from "https://www.gstatic.com/firebasejs/12.14.0/firebase-app.js";
    import { getDatabase, onValue, ref, set, update } from "https://www.gstatic.com/firebasejs/12.14.0/firebase-database.js";

    const firebaseConfig = {
      apiKey: "AIzaSyBKX09qG0tWc3lz1I-vrC3l_Nzu6SIQZyU",
      authDomain: "sistemaambienteinteligente.firebaseapp.com",
      databaseURL: "https://sistemaambienteinteligente-default-rtdb.firebaseio.com",
      projectId: "sistemaambienteinteligente",
      storageBucket: "sistemaambienteinteligente.firebasestorage.app",
      messagingSenderId: "384427074485",
      appId: "1:384427074485:web:3b93bd244f6ce6c93caffc",
      measurementId: "G-79W4NJSJJS"
    };

    const app = initializeApp(firebaseConfig);
    const database = getDatabase(app);

    const firebaseStatus = document.getElementById("firebaseStatus");
    const tempEl = document.getElementById("temp");
    const lumEl = document.getElementById("lum");
    const ledStatusEl = document.getElementById("ledStatus");
    const modoStatusEl = document.getElementById("modoStatus");
    const rgbStatusEl = document.getElementById("rgbStatus");
    const colorPicker = document.getElementById("colorPicker");
    const commandStatus = document.getElementById("commandStatus");
    const commandButtons = [
      document.getElementById("ledOn"),
      document.getElementById("ledOff"),
      document.getElementById("buzzer"),
      document.getElementById("applyColor"),
      document.getElementById("btnAbrirModalProjetor"), // NOVO
      document.getElementById("btnAbrirModalAc"),       // NOVO
      document.getElementById("btnAcLearn")
    ];

    let firebaseConnected = false;
    let recebeuSensores = false;
    let recebeuEstado = false;
    let acLearning = false;
    let acPower = false;

    function setStatus(texto, tipo = "") {
      firebaseStatus.textContent = texto;
      firebaseStatus.className = `status ${tipo}`.trim();
    }

    function atualizarStatusGeral() {
      if (!firebaseConnected) {
        setStatus("Offline", "error");
        return;
      }

      if (!recebeuSensores || !recebeuEstado) {
        setStatus("Sem ESP32", "warning");
        return;
      }

      setStatus("Online", "online");
    }

    function setCommandStatus(texto, tipo = "") {
      commandStatus.textContent = texto;
      commandStatus.className = `message ${tipo}`.trim();
    }

    async function enviarComando(acao, textoSucesso) {
      commandButtons.forEach((button) => button.disabled = true);
      setCommandStatus("Enviando comando...");

      try {
        await acao();
        setCommandStatus(textoSucesso, "ok");
      } catch (error) {
        console.error(error);
        setCommandStatus(`Erro no Firebase: ${error.message}`, "error");
      } finally {
        commandButtons.forEach((button) => button.disabled = false);
      }
    }

    function colorToRgb(hex) {
      return {
        r: parseInt(hex.slice(1, 3), 16),
        g: parseInt(hex.slice(3, 5), 16),
        b: parseInt(hex.slice(5, 7), 16)
      };
    }

    function rgbToColor({ r = 0, g = 0, b = 0 }) {
      return "#" + [r, g, b]
        .map((value) => Number(value).toString(16).padStart(2, "0"))
        .join("");
    }

    onValue(ref(database, ".info/connected"), (snapshot) => {
      firebaseConnected = snapshot.val() === true;
      atualizarStatusGeral();
    });

    onValue(ref(database, "ambiente/sensores"), (snapshot) => {
      if (!snapshot.exists()) {
        recebeuSensores = false;
        tempEl.textContent = "--";
        lumEl.textContent = "--";
        atualizarStatusGeral();
        return;
      }

      recebeuSensores = true;
      const dados = snapshot.val();
      const temperatura = Number(dados.temperatura);

      tempEl.textContent = Number.isFinite(temperatura)
        ? `${temperatura.toFixed(1)} C`
        : "--";

      lumEl.textContent = dados.luminosidade ?? "--";
      atualizarStatusGeral();
    }, (error) => {
      console.error(error);
      setStatus("Erro RTDB", "error");
    });

    onValue(ref(database, "ambiente/estado"), (snapshot) => {
      if (!snapshot.exists()) {
        recebeuEstado = false;
        ledStatusEl.textContent = "--";
        modoStatusEl.textContent = "Aguardando ESP32";
        rgbStatusEl.textContent = "RGB: --";
        atualizarStatusGeral();
        return;
      }

      recebeuEstado = true;
      const estado = snapshot.val();
      const rgb = estado.rgb || {};
      const ac = estado.ac || {};
      acPower = Boolean(ac.power);

      ledStatusEl.textContent = estado.led ? "LIGADO" : "DESLIGADO";
      modoStatusEl.textContent = `Modo: ${estado.modoAutomatico ? "automatico" : "manual"}`;
      rgbStatusEl.textContent = `RGB: ${rgb.r ?? 0}, ${rgb.g ?? 0}, ${rgb.b ?? 0}`;
      colorPicker.value = rgbToColor(rgb);
      atualizarStatusGeral();
    }, (error) => {
      console.error(error);
      setStatus("Erro RTDB", "error");
    });

    document.getElementById("ledOn").addEventListener("click", async () => {
      await enviarComando(
        () => update(ref(database, "ambiente/comandos"), {
          led: true,
          ledManual: true
        }),
        "Comando enviado: ligar LED"
      );
    });

    document.getElementById("ledOff").addEventListener("click", async () => {
      await enviarComando(
        () => update(ref(database, "ambiente/comandos"), {
          led: false,
          ledManual: true
        }),
        "Comando enviado: desligar LED"
      );
    });

    document.getElementById("buzzer").addEventListener("click", async () => {
      await enviarComando(
        () => set(ref(database, "ambiente/comandos/buzzer"), true),
        "Comando enviado: testar buzzer"
      );
    });

    document.getElementById("applyColor").addEventListener("click", async () => {
      await enviarComando(
        () => update(ref(database, "ambiente/comandos/rgb"), colorToRgb(colorPicker.value)),
        "Comando enviado: atualizar RGB"
      );
    });

    // Lgica do Modal do Projetor
    const modalOverlay = document.getElementById("modalOverlay");
    
    document.getElementById("btnAbrirModalProjetor").addEventListener("click", () => {
      modalOverlay.classList.add("active");
    });

    document.getElementById("btnFecharModal").addEventListener("click", () => {
      modalOverlay.classList.remove("active");
    });

    // Mapeia todos os botes do controle remoto
    document.querySelectorAll(".remote-btn").forEach(btn => {
      btn.addEventListener("click", async (e) => {
        const comando = e.currentTarget.getAttribute("data-cmd");
        if(comando) {
          await enviarComando(
            () => set(ref(database, "ambiente/comandos/projetorCmd"), comando),
            `Comando enviado: Projetor ${comando.toUpperCase()}`
          );
        }
      });
    });

    // Lgica do Modal do Ar Condicionado
    const modalOverlayAc = document.getElementById("modalOverlayAc");
    
    document.getElementById("btnAbrirModalAc").addEventListener("click", () => {
      modalOverlayAc.classList.add("active");
    });

    document.getElementById("btnFecharModalAc").addEventListener("click", () => {
      modalOverlayAc.classList.remove("active");
    });

    const btnAcLearn = document.getElementById("btnAcLearn");

    btnAcLearn.addEventListener("click", () => {
      acLearning = !acLearning;
      btnAcLearn.style.background = acLearning ? "#33c481" : "#f4c95d";
      btnAcLearn.style.color = acLearning ? "#062015" : "#332400";
      setCommandStatus(acLearning ? "Gravando AC" : "");
    });

    // Mapeia todos os botes do controle do Ar Condicionado
    document.querySelectorAll("[data-cmd-ac]").forEach(btn => {
      btn.addEventListener("click", async (e) => {
        const comando = e.currentTarget.getAttribute("data-cmd-ac");
        if(comando) {
          const aprendendo = acLearning;
          const path = aprendendo ? "ambiente/comandos/acLearnCmd" : "ambiente/comandos/acCmd";
          const comandoFirebase = aprendendo && comando === "power" && acPower ? "powerOff" : comando;

          await enviarComando(
            () => set(ref(database, path), comandoFirebase),
            aprendendo ? `Gravao selecionada: ${comando.toUpperCase()}` : `Comando enviado: AC ${comando.toUpperCase()}`
          );

          if (aprendendo) {
            acLearning = false;
            btnAcLearn.style.background = "#f4c95d";
            btnAcLearn.style.color = "#332400";
          }
        }
      });
    });
  </script>
</body>

</html>

main.cpp

C/C++
Main code implementation for ESP32 functionality
#include <Arduino.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <IRremoteESP8266.h>
#include <IRrecv.h>
#include <IRsend.h>
#include <IRutils.h>
#include <ir_Carrier.h> 
#include <receptor.cpp>

const char *WIFI_SSID = "your-WiFi-SSID-here";
const char *WIFI_PASSWORD = "Your-WiFi-Password-here";

#define API_KEY "Your-API-Key-here"
#define DATABASE_URL "Your-Firebase-Realtime-Database-URL-here"
#define DNS_PRIMARY IPAddress(8, 8, 8, 8)
#define DNS_SECONDARY IPAddress(1, 1, 1, 1)

// ================= PINOS =================

#define LED_PIN 18
#define LDR_PIN 34
#define THERM_PIN 35
#define BUZZER_PIN 16

#define RED_PIN 25
#define GREEN_PIN 26
#define BLUE_PIN 27

const uint16_t kRecvPin = 19;
const uint16_t kIrLed = 4;

// Configurao avanada para capturar ar-condicionado (buffer maior e timeout longo)
const uint16_t kCaptureBufferSize = 1024;
const uint8_t kTimeout = 50; 
IRrecv irrecv(kRecvPin, kCaptureBufferSize, kTimeout, true);

IRsend irsend(kIrLed);
IRCarrierAc64 ac(kIrLed);
decode_results results;

// ================= INSTRUES PARA CAPTURAR CDIGO DO AC =================
// 1. Faa upload deste cdigo no ESP32
// 2. Abra o Monitor Serial (115200 baud)
// 3. Aponte o controle remoto do CARRIER para o receptor IR (pino 19)
// 4. Pressione o boto "LIGAR" do controle
// 5. No Monitor Serial, voc ver algo como:
//    
//    === SINAL IR RECEBIDO ===
//    Tipo: UNKNOWN
//    Cdigo HEX: 0x33CDDA66
//    Bits: 32
//    
//    Copie o cdigo abaixo:
//    uint16_t rawData[68] = { 9020, 4500, 560, 560, ... };
//    ========================
//
// 6. Copie a linha "uint16_t rawData[X] = { ... };"
// 7. Cole no lugar de `codigoACRaw` abaixo
// 8. Crie a vriavel com o tamanho: `const uint16_t codigoACRawLen = X;`
// 9. Descomente as linhas e faa novo upload
// ============================================================================

// ================= FIREBASE =================

const char *FIREBASE_HOST = "https://sistemaambienteinteligente-default-rtdb.firebaseio.com";

HTTPClient http;

const char *SENSORES_PATH = "/ambiente/sensores";
const char *ESTADO_PATH = "/ambiente/estado";
const char *COMANDOS_PATH = "/ambiente/comandos";
const char *IR_CAPTURE_PATH = "/ambiente/ir/ultimo";
const char *AC_RAW_PATH = "/ambiente/acRaw";

String caminhoComando(const char *subPath)
{
  String path(COMANDOS_PATH);
  path.concat(subPath);
  return path;
}

String caminhoAcRaw(const String &cmd, const char *subPath)
{
  String path(AC_RAW_PATH);
  path.concat("/");
  path.concat(cmd);
  path.concat(subPath);
  return path;
}

// ================= VARIAVEIS =================

bool ledStatus = false;
bool modoAutomatico = true;
bool buzzerLigado = false;
bool alertaTemperaturaAnterior = false;

int luminosidade = 0;
float temperatura = 0;

unsigned long ultimoComandoManual = 0;
unsigned long ultimoEnvioFirebase = 0;
unsigned long ultimaLeituraComandos = 0;
unsigned long ultimoDebugSerial = 0;
unsigned long buzzerManualAte = 0;
String comandoAcAprendizado = "";

const unsigned long TEMPO_MANUAL = 5000;
const unsigned long INTERVALO_FIREBASE = 3000; // 1000
const unsigned long INTERVALO_COMANDOS = 2000; // 500
const unsigned long INTERVALO_DEBUG = 2000;
const int LIMITE_LUMINOSIDADE = 2100;
const float LIMITE_TEMPERATURA = 35.0;
const uint16_t AC_IR_REPEATS = 2;

int redValue = 0;
int greenValue = 0;
int blueValue = 0;

// ================= CDIGOS IR DO PROJETOR (BPC-1080P M18-W) =================
// Substitua 0x0 pelo cdigo HEX capturado no Monitor Serial
uint64_t projPower = 0xFB38C7;
uint64_t projMute = 0xFB10EF;
uint64_t projPlayPause = 0xFB807F;
uint64_t projSource = 0xFBDA25;
uint64_t projMenu = 0xFB827D;

uint64_t projUp = 0xFB58A7;
uint64_t projDown = 0xFB12ED;
uint64_t projLeft = 0xFBE21D;
uint64_t projRight = 0xFBE01F;
uint64_t projOk = 0xFB609F;

uint64_t projRewind = 0xFB18E7;
uint64_t projForward = 0xFBC03F;
uint64_t projBack = 0xFB50AF;

uint64_t projPrev = 0xFBF20D;
uint64_t projNext = 0xFBD22D;
// ============================================================================

// ================= AR CONDICIONADO (CARRIER) =================

const uint16_t acPowerOnRaw[] = {
  9006, 4452, 528, 602, 532, 578, 554, 1678, 556, 578, 554, 1678, 580,
  578, 532, 600, 554, 574, 576, 580, 532, 1682, 550, 602, 530, 578,
  554, 1678, 556, 578, 554, 602, 530, 598, 552, 580, 554, 602, 530,
  602, 532, 580, 552, 602, 532, 580, 552, 578, 556, 596, 554, 580,
  554, 580, 552, 578, 554, 578, 556, 580, 552, 578, 556, 576, 556,
  594, 556, 578, 554, 578, 554, 580, 554, 578, 554, 576, 556, 578,
  554, 578, 556, 598, 556, 1678, 556, 578, 554, 578, 556, 1666, 556
};
const uint16_t acPowerOnRawLen = sizeof(acPowerOnRaw) / sizeof(acPowerOnRaw[0]);
const uint16_t AC_RAW_FREQUENCY_KHZ = 38;
const uint16_t AC_RAW_MAX_LENGTH = 300;

String uint64ParaHex(uint64_t valor)
{
  String texto = "0x";
  texto += String((uint32_t)(valor >> 32), HEX);
  String parteBaixa = String((uint32_t)valor, HEX);

  while (parteBaixa.length() < 8)
  {
    parteBaixa = "0" + parteBaixa;
  }

  texto += parteBaixa;
  texto.toUpperCase();
  return texto;
}

const char *modoAcTexto(uint8_t modo)
{
  if (modo == kCarrierAc64Cool) return "cool";
  if (modo == kCarrierAc64Fan) return "fan";
  if (modo == kCarrierAc64Heat) return "heat";
  return "unknown";
}

const char *fanAcTexto(uint8_t fan)
{
  if (fan == kCarrierAc64FanAuto) return "auto";
  if (fan == kCarrierAc64FanLow) return "low";
  if (fan == kCarrierAc64FanMedium) return "medium";
  if (fan == kCarrierAc64FanHigh) return "high";
  return "unknown";
}

void preencherEstadoAc(JsonObject estadoAc)
{
  estadoAc["power"] = ac.getPower();
  estadoAc["temp"] = ac.getTemp();
  estadoAc["mode"] = modoAcTexto(ac.getMode());
  estadoAc["fan"] = fanAcTexto(ac.getFan());
  estadoAc["swingV"] = ac.getSwingV();
  estadoAc["sleep"] = ac.getSleep();
  estadoAc["raw"] = uint64ParaHex(ac.getRaw());
}

void enviarComandoAc(const String &cmdAc)
{
  Serial.print("Enviando AC: ");
  Serial.println(cmdAc);
  Serial.println(ac.toString());
  Serial.print("Raw CarrierAc64: ");
  Serial.println(uint64ParaHex(ac.getRaw()));

  ac.send(AC_IR_REPEATS);
}

void enviarRawAc(const String &cmdAc, const uint16_t rawData[], uint16_t rawLen)
{
  Serial.print("Enviando AC RAW: ");
  Serial.print(cmdAc);
  Serial.print(" | len=");
  Serial.print(rawLen);
  Serial.print(" | freq=");
  Serial.print(AC_RAW_FREQUENCY_KHZ);
  Serial.println("kHz");

  irsend.sendRaw(rawData, rawLen, AC_RAW_FREQUENCY_KHZ);
}

bool csvParaRaw(const String &rawCsv, uint16_t rawData[], uint16_t &rawLen)
{
  rawLen = 0;
  uint32_t valor = 0;
  bool lendoNumero = false;

  for (uint16_t i = 0; i <= rawCsv.length(); i++)
  {
    char c = (i < rawCsv.length()) ? rawCsv[i] : ',';

    if (c >= '0' && c <= '9')
    {
      lendoNumero = true;
      valor = (valor * 10) + (c - '0');
      if (valor > UINT16_MAX)
      {
        return false;
      }
    }
    else if (c == ',' || c == ' ' || c == '\n' || c == '\r' || c == '\t')
    {
      if (lendoNumero)
      {
        if (rawLen >= AC_RAW_MAX_LENGTH)
        {
          return false;
        }

        rawData[rawLen++] = (uint16_t)valor;
        valor = 0;
        lendoNumero = false;
      }
    }
    else
    {
      return false;
    }
  }

  return rawLen > 0;
}

// ================= RGB =================

void atualizarRGB()
{
  ledcWrite(0, redValue);
  ledcWrite(1, greenValue);
  ledcWrite(2, blueValue);
}

int limitarCor(int valor)
{
  return constrain(valor, 0, 255);
}

// ================= BUZZER =================

void definirBuzzer(bool ligar)
{
  if (buzzerLigado == ligar)
  {
    return;
  }

  buzzerLigado = ligar;
  digitalWrite(BUZZER_PIN, ligar ? HIGH : LOW);
}

// ================= WIFI =================

void conectarWifi()
{
  WiFi.mode(WIFI_STA);
  WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE, DNS_PRIMARY, DNS_SECONDARY);
  
  Serial.print("WiFi SSID: ");
  Serial.println(WIFI_SSID);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);

  Serial.print("Conectando ao WiFi");

  int tentativas = 0;
  while (WiFi.status() != WL_CONNECTED && tentativas < 20)
  {
    delay(500);
    Serial.print(".");
    tentativas++;
  }

  Serial.println();
  
  if (WiFi.status() == WL_CONNECTED) {
    Serial.print("WiFi conectado! IP: ");
    Serial.println(WiFi.localIP());
    
    // Testar DNS
    IPAddress firebaseIp;
    if (WiFi.hostByName(DATABASE_URL, firebaseIp))
    {
      Serial.print("DNS OK - Firebase IP: ");
      Serial.println(firebaseIp);
    }
    else
    {
      Serial.println("ERRO: DNS falhou para Firebase");
    }
  } else {
    Serial.println("ERRO: Falha ao conectar WiFi");
  }
}

// ================= FIREBASE =================

void conectarFirebase()
{
  Serial.println("Firebase via REST API pronto");
}

bool enviarParaFirebase(const String &path, const String &payload)
{
  if (WiFi.status() != WL_CONNECTED)
  {
    return false;
  }

  String url = String(FIREBASE_HOST) + path + ".json";
  
  http.begin(url);
  http.addHeader("Content-Type", "application/json");
  
  int httpResponseCode = http.PUT(payload);
  
  if (httpResponseCode != 200)
  {
    Serial.print("ERRO: HTTP ");
    Serial.print(httpResponseCode);
    Serial.print(" - ");
    Serial.println(url);
  }
  
  http.end();
  return (httpResponseCode == 200);
}

String lerDoFirebase(const String &path)
{
  if (WiFi.status() != WL_CONNECTED)
  {
    return "";
  }

  String url = String(FIREBASE_HOST) + path + ".json";
  
  http.begin(url);
  http.addHeader("Content-Type", "application/json");
  
  int httpResponseCode = http.GET();
  String response = "";
  
  if (httpResponseCode == 200)
  {
    response = http.getString();
  }
  else
  {
    Serial.print("ERRO ao ler: HTTP ");
    Serial.println(httpResponseCode);
  }
  
  http.end();
  return response;
}

String rawCapturadoParaCsv(const decode_results &captura, uint16_t &rawLenCorrigido)
{
  String rawCsv;
  rawLenCorrigido = getCorrectedRawLength(&captura);
  rawCsv.reserve(rawLenCorrigido * 7);

  uint16_t pos = 0;
  for (uint16_t i = 1; i < captura.rawlen; i++)
  {
    uint32_t usecs = captura.rawbuf[i] * kRawTick;

    while (usecs > UINT16_MAX)
    {
      if (pos > 0) rawCsv += ',';
      rawCsv += String(UINT16_MAX);
      rawCsv += ",0";
      pos += 2;
      usecs -= UINT16_MAX;
    }

    if (pos > 0) rawCsv += ',';
    rawCsv += String(usecs);
    pos++;
  }

  return rawCsv;
}

bool publicarRawAcAprendido(const String &cmdAc, const decode_results &captura, const String &rawCsv, uint16_t rawLen)
{
  if (cmdAc.length() == 0 || rawCsv.length() == 0)
  {
    return false;
  }

  String payload;
  payload.reserve(rawCsv.length() + 180);
  payload += "{\"protocol\":\"";
  payload += typeToString(captura.decode_type, captura.repeat);
  payload += "\",\"bits\":";
  payload += String(captura.bits);
  payload += ",\"value\":\"";
  payload += uint64ParaHex(captura.value);
  payload += "\",\"rawlen\":";
  payload += String(rawLen);
  payload += ",\"raw\":\"";
  payload += rawCsv;
  payload += "\",\"atualizadoEm\":";
  payload += String(millis());
  payload += "}";

  return enviarParaFirebase(caminhoAcRaw(cmdAc, ""), payload);
}

void publicarCapturaIr(const decode_results &captura)
{
  if (WiFi.status() != WL_CONNECTED)
  {
    return;
  }

  uint16_t rawLenCorrigido = 0;
  String rawCsv = rawCapturadoParaCsv(captura, rawLenCorrigido);

  String payload;
  payload.reserve(rawCsv.length() + 180);
  payload += "{\"protocol\":\"";
  payload += typeToString(captura.decode_type, captura.repeat);
  payload += "\",\"bits\":";
  payload += String(captura.bits);
  payload += ",\"value\":\"";
  payload += uint64ParaHex(captura.value);
  payload += "\",\"rawlen\":";
  payload += String(rawLenCorrigido);
  payload += ",\"raw\":\"";
  payload += rawCsv;
  payload += "\",\"atualizadoEm\":";
  payload += String(millis());
  payload += "}";

  enviarParaFirebase(IR_CAPTURE_PATH, payload);

  if (comandoAcAprendizado.length() > 0)
  {
    Serial.print("Salvando RAW aprendido para AC: ");
    Serial.println(comandoAcAprendizado);

    if (publicarRawAcAprendido(comandoAcAprendizado, captura, rawCsv, rawLenCorrigido))
    {
      enviarParaFirebase(caminhoComando("/acLearnCmd"), "\"\"");
      comandoAcAprendizado = "";
    }
  }
}

void exibirCapturaIr(const decode_results &captura)
{
  Serial.println("\n=== SINAL IR RECEBIDO ===");
  Serial.print("Tipo: ");
  Serial.println(typeToString(captura.decode_type, captura.repeat));
  Serial.print("Codigo HEX: ");
  Serial.println(uint64ParaHex(captura.value));
  Serial.print("Bits: ");
  Serial.println(captura.bits);
  Serial.print("Raw length: ");
  Serial.println(getCorrectedRawLength(&captura));

  Serial.println("\nCopie o codigo abaixo:");
  Serial.println(resultToSourceCode(&captura));
  Serial.println("========================\n");
}

bool firebaseDisponivel()
{
  return WiFi.status() == WL_CONNECTED;
}

void publicarEstadoInicial()
{
  if (!firebaseDisponivel())
  {
    return;
  }

  Serial.println("Publicando estado inicial...");
  
  StaticJsonDocument<256> doc;
  doc["ledManual"] = false;
  doc["led"] = false;
  doc["buzzer"] = false;
  JsonObject rgb = doc.createNestedObject("rgb");
  rgb["r"] = redValue;
  rgb["g"] = greenValue;
  rgb["b"] = blueValue;
  
  String payload;
  serializeJson(doc, payload);
  
  enviarParaFirebase(COMANDOS_PATH, payload);
}

void publicarDadosFirebase()
{
  if (!firebaseDisponivel())
  {
    return;
  }

  // Publicar sensores
  StaticJsonDocument<256> sensores;
  sensores["luminosidade"] = luminosidade;
  sensores["temperatura"] = temperatura;
  sensores["atualizadoEm"] = millis();
  
  String sensorPayload;
  serializeJson(sensores, sensorPayload);
  enviarParaFirebase(SENSORES_PATH, sensorPayload);

  // Publicar estado
  StaticJsonDocument<512> estado;
  estado["led"] = ledStatus;
  estado["modoAutomatico"] = modoAutomatico;
  estado["buzzer"] = buzzerLigado;
  JsonObject rgbState = estado.createNestedObject("rgb");
  rgbState["r"] = redValue;
  rgbState["g"] = greenValue;
  rgbState["b"] = blueValue;
  JsonObject acState = estado.createNestedObject("ac");
  preencherEstadoAc(acState);
  
  String estadoPayload;
  serializeJson(estado, estadoPayload);
  enviarParaFirebase(ESTADO_PATH, estadoPayload);
}

bool lerBoolFirebase(const String &path, bool valorPadrao)
{
  String response = lerDoFirebase(path);
  
  if (response.length() == 0)
  {
    return valorPadrao;
  }
  
  if (response == "true") return true;
  if (response == "false") return false;
  
  return valorPadrao;
}

int lerIntFirebase(const String &path, int valorPadrao)
{
  String response = lerDoFirebase(path);
  
  if (response.length() == 0)
  {
    return valorPadrao;
  }
  
  return response.toInt();
}

String lerStringFirebase(const String &path, String valorPadrao) {
  String response = lerDoFirebase(path);
  
  if (response.length() == 0 || response == "null") {
    return valorPadrao;
  }
  
  response.replace("\"", ""); // Remove as aspas devolvidas pelo JSON
  return response;
}

bool enviarRawAcSalvo(const String &cmdAc)
{
  String rawCsv = lerStringFirebase(caminhoAcRaw(cmdAc, "/raw"), "");

  if (rawCsv.length() == 0)
  {
    Serial.print("RAW do AC ainda nao cadastrado para: ");
    Serial.println(cmdAc);
    return false;
  }

  uint16_t rawData[AC_RAW_MAX_LENGTH];
  uint16_t rawLen = 0;

  if (!csvParaRaw(rawCsv, rawData, rawLen))
  {
    Serial.print("RAW do AC invalido para: ");
    Serial.println(cmdAc);
    return false;
  }

  enviarRawAc(cmdAc, rawData, rawLen);
  return true;
}

void lerComandosFirebase()
{
  if (!firebaseDisponivel())
  {
    return;
  }

  // ===== CAPTURAR CDIGOS IR =====
  // Descomente para clonar controle do Carrier
  
  if (false && irrecv.decode(&results)) {
    Serial.print("Cdigo capturado: 0x");
    Serial.println(results.value, HEX);
    Serial.print("Protocolo: ");
    Serial.println(results.decode_type);
    irrecv.resume();
  }
  

  String novoComandoAcAprendizado = lerStringFirebase(caminhoComando("/acLearnCmd"), "");

  if (novoComandoAcAprendizado.length() > 0 && novoComandoAcAprendizado != comandoAcAprendizado)
  {
    comandoAcAprendizado = novoComandoAcAprendizado;
    Serial.print("Modo aprendizado AC aguardando captura para: ");
    Serial.println(comandoAcAprendizado);
  }

  bool ledManual = lerBoolFirebase(caminhoComando("/ledManual"), false);

  if (ledManual)
  {
    ledStatus = lerBoolFirebase(caminhoComando("/led"), ledStatus);
    digitalWrite(LED_PIN, ledStatus ? HIGH : LOW);

    modoAutomatico = false;
    ultimoComandoManual = millis();

    // Resetar comando
    enviarParaFirebase(caminhoComando("/ledManual"), "false");
  }

  bool testarBuzzer = lerBoolFirebase(caminhoComando("/buzzer"), false);

  if (testarBuzzer)
  {
    buzzerManualAte = millis() + 300;
    enviarParaFirebase(caminhoComando("/buzzer"), "false");
  }

  int novoR = limitarCor(lerIntFirebase(caminhoComando("/rgb/r"), redValue));
  int novoG = limitarCor(lerIntFirebase(caminhoComando("/rgb/g"), greenValue));
  int novoB = limitarCor(lerIntFirebase(caminhoComando("/rgb/b"), blueValue));

  if (novoR != redValue || novoG != greenValue || novoB != blueValue)
  {
    redValue = novoR;
    greenValue = novoG;
    blueValue = novoB;
    atualizarRGB();
  }

  // ================= COMANDOS DO PROJETOR =================
  String cmdProjetor = lerStringFirebase(caminhoComando("/projetorCmd"), "");
  
  if (cmdProjetor.length() > 0) {
    Serial.print("Comando do Projetor Recebido: ");
    Serial.println(cmdProjetor);

    // Mapeamento dos botes (envia o protocolo NEC, que  padro para a maioria dos projetores)
    if (cmdProjetor == "power") irsend.sendNEC(projPower, 32);
    else if (cmdProjetor == "mute") irsend.sendNEC(projMute, 32);
    else if (cmdProjetor == "play") irsend.sendNEC(projPlayPause, 32);
    else if (cmdProjetor == "source") irsend.sendNEC(projSource, 32);
    else if (cmdProjetor == "menu") irsend.sendNEC(projMenu, 32);
    else if (cmdProjetor == "up") irsend.sendNEC(projUp, 32);
    else if (cmdProjetor == "down") irsend.sendNEC(projDown, 32);
    else if (cmdProjetor == "left") irsend.sendNEC(projLeft, 32);
    else if (cmdProjetor == "right") irsend.sendNEC(projRight, 32);
    else if (cmdProjetor == "ok") irsend.sendNEC(projOk, 32);
    else if (cmdProjetor == "rewind") irsend.sendNEC(projRewind, 32);
    else if (cmdProjetor == "forward") irsend.sendNEC(projForward, 32);
    else if (cmdProjetor == "back") irsend.sendNEC(projBack, 32);
    else if (cmdProjetor == "prev") irsend.sendNEC(projPrev, 32);
    else if (cmdProjetor == "next") irsend.sendNEC(projNext, 32);

    // Limpa o comando no Firebase escrevendo uma string vazia ("")
    enviarParaFirebase(caminhoComando("/projetorCmd"), "\"\""); 
  }

  // ================= COMANDOS DO AR CONDICIONADO (CARRIER) =================
  String cmdAc = lerStringFirebase(caminhoComando("/acCmd"), "");
  
  if (cmdAc.length() > 0) {
    Serial.print("Comando do AC Recebido: ");
    Serial.println(cmdAc);

    bool comandoAcReconhecido = true;
    bool enviarCarrierAc = true;
    bool comandoAcEnviado = false;

    // Altera o estado interno do ESP32 baseado no boto pressionado
    if (cmdAc == "power") {
      enviarCarrierAc = false;
      if (ac.getPower())
      {
        comandoAcEnviado = enviarRawAcSalvo("powerOff");
        if (comandoAcEnviado) ac.off();
        else
        {
          comandoAcEnviado = enviarRawAcSalvo("power");
          if (comandoAcEnviado) ac.off();
        }
        if (!comandoAcEnviado)
        {
          Serial.println("RAW powerOff nao cadastrado; usando powerOn como fallback.");
          ac.on();
          enviarRawAc("powerOn", acPowerOnRaw, acPowerOnRawLen);
          comandoAcEnviado = true;
        }
      }
      else
      {
        ac.on();
        enviarRawAc("powerOn", acPowerOnRaw, acPowerOnRawLen);
        comandoAcEnviado = true;
      }
    } 
    else if (cmdAc == "tempUp") {
      ac.on();
      ac.setTemp(ac.getTemp() + 1); // Aumenta a temperatura
    } 
    else if (cmdAc == "tempDown") {
      ac.on();
      ac.setTemp(ac.getTemp() - 1); // Diminui a temperatura
    } 
    else if (cmdAc == "mode") {
      ac.on();
      // Alterna entre Frio -> Desumidificar -> Automtico -> Quente -> Ventilao
      uint8_t mode = ac.getMode();
      if (mode == kCarrierAc64Cool) ac.setMode(kCarrierAc64Fan);
      else if (mode == kCarrierAc64Fan) ac.setMode(kCarrierAc64Heat);
      else ac.setMode(kCarrierAc64Cool);
    } 
    else if (cmdAc == "fan") {
      ac.on();
      // Alterna velocidade: Auto -> Baixa -> Mdia -> Alta
      uint8_t fan = ac.getFan();
      if (fan == kCarrierAc64FanAuto) ac.setFan(kCarrierAc64FanLow);
      else if (fan == kCarrierAc64FanLow) ac.setFan(kCarrierAc64FanMedium);
      else if (fan == kCarrierAc64FanMedium) ac.setFan(kCarrierAc64FanHigh);
      else ac.setFan(kCarrierAc64FanAuto);
    } 
    else if (cmdAc == "sweep") {
      ac.on();
      ac.setSwingV(!ac.getSwingV()); // Alterna o movimento vertical das aletas
    }
    else if (cmdAc == "sleep") {
      ac.on();
      ac.setSleep(!ac.getSleep());
    }
    else if (cmdAc == "louver") {
      ac.setSleep(false);
      ac.setOnTimer(0);
      ac.setOffTimer(0);
    }
    else {
      comandoAcReconhecido = false;
      Serial.print("Comando do AC desconhecido: ");
      Serial.println(cmdAc);
    }

    if (comandoAcReconhecido && enviarCarrierAc && !comandoAcEnviado)
    {
      comandoAcEnviado = enviarRawAcSalvo(cmdAc);
    }

    if (comandoAcReconhecido && !comandoAcEnviado)
    {
      Serial.print("Comando AC reconhecido, mas sem RAW cadastrado: ");
      Serial.println(cmdAc);
    }

    // Limpa o comando no Firebase escrevendo uma string vazia ("")
    enviarParaFirebase(caminhoComando("/acCmd"), "\"\""); 
  }
  
}

// ================= CONTROLE =================

void lerSensores()
{
  luminosidade = analogRead(LDR_PIN);

  int leituraTemp = analogRead(THERM_PIN);
  temperatura = map(leituraTemp, 0, 4095, 0, 50);
}

void atualizarAutomatico()
{
  if (!modoAutomatico && (millis() - ultimoComandoManual) > TEMPO_MANUAL)
  {
    modoAutomatico = true;
  }

  if (!modoAutomatico)
  {
    return;
  }

  ledStatus = luminosidade < LIMITE_LUMINOSIDADE;
  digitalWrite(LED_PIN, ledStatus ? HIGH : LOW);
}

void atualizarAlertas()
{
  bool alertaTemperatura = temperatura > LIMITE_TEMPERATURA;
  bool alertaEscuridao = modoAutomatico && luminosidade < LIMITE_LUMINOSIDADE;
  bool buzzerManual = millis() < buzzerManualAte;

  if (alertaTemperatura && !alertaTemperaturaAnterior)
  {
    Serial.println("ALERTA: TEMPERATURA ALTA");
  }

  alertaTemperaturaAnterior = alertaTemperatura;

  definirBuzzer(alertaTemperatura || alertaEscuridao || buzzerManual);
}

void exibirDebugSerial()
{
  Serial.print("Luminosidade: ");
  Serial.println(luminosidade);
  Serial.print("Temperatura: ");
  Serial.println(temperatura);
  Serial.print("LED: ");
  Serial.println(ledStatus ? "LIGADO" : "DESLIGADO");
  Serial.print("Modo: ");
  Serial.println(modoAutomatico ? "AUTOMATICO" : "MANUAL");
}

// ================= SETUP =================

void setup()
{
  Serial.begin(115200);

  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);

  pinMode(BUZZER_PIN, OUTPUT);
  digitalWrite(BUZZER_PIN, LOW);

  ledcSetup(0, 5000, 8);
  ledcAttachPin(RED_PIN, 0);

  ledcSetup(1, 5000, 8);
  ledcAttachPin(GREEN_PIN, 1);

  ledcSetup(2, 5000, 8);
  ledcAttachPin(BLUE_PIN, 2);

  atualizarRGB();

  conectarWifi();
  conectarFirebase();
  publicarEstadoInicial();

  // Inicializa o receptor e o Transmissor IR
  irrecv.enableIRIn();
  irsend.begin();

  // Inicializa o estado virtual do Ar Condicinado Carrier
  ac.begin();
  ac.off();
  ac.setTemp(24);
  ac.setMode(kCarrierAc64Cool); 
  ac.setFan(kCarrierAc64FanAuto);
  ac.setSwingV(false);
  ac.setSleep(false);

  Serial.println("Mdulos IR e AC Carrier inicializados. Aguardando sinais para clonar...");
}

// ================= LOOP =================

void loop()
{
  if (irrecv.decode(&results)) {
    Serial.println("\n=== SINAL IR RECEBIDO ===");
    Serial.print("Tipo: ");
    Serial.println(typeToString(results.decode_type));
    Serial.print("Cdigo HEX: 0x");
    Serial.println(results.value, HEX);
    Serial.print("Bits: ");
    Serial.println(results.bits);
    
    // Mostrar dados RAW j no formato C++
    Serial.println("\nCopie o cdigo abaixo:");
    Serial.println(resultToSourceCode(&results));
    publicarCapturaIr(results);
    Serial.println("========================\n");
    
    irrecv.resume(); // Pronto para o prximo sinal
  }
  
  lerSensores();
  atualizarAutomatico();
  atualizarAlertas();

  unsigned long agora = millis();

  if (agora - ultimaLeituraComandos >= INTERVALO_COMANDOS)
  {
    ultimaLeituraComandos = agora;
    lerComandosFirebase();
  }

  if (agora - ultimoEnvioFirebase >= INTERVALO_FIREBASE)
  {
    ultimoEnvioFirebase = agora;
    publicarDadosFirebase();
  }

  if (agora - ultimoDebugSerial >= INTERVALO_DEBUG)
  {
    ultimoDebugSerial = agora;
    exibirDebugSerial();
  }

  delay(20);
}

Credits

Lázaro Santos
2 projects • 1 follower
Fernando F de Carvalho
27 projects • 8 followers
KEVIN LUIS ALVES DE LIMA
1 project • 0 followers
Edson Freitas
1 project • 0 followers

Comments