Início Etapa 2 Atividade 8
8
ETAPA 2 - MOBILE

LED via Mobile 📱💡

Controle o LED do ESP32 usando um botão no seu celular!

🎯 Objetivo desta atividade

Criar um sistema completo onde um app no celular envia comandos para ligar/desligar o LED do ESP32. O ESP32 vai consultar uma API PHP para receber os comandos via WiFi.

🏗️ Como funciona?

📱

App Mobile

Expo Snack

🖥️

API PHP

Servidor XAMPP

📟

ESP32

Arduino IDE

📱 🖥️ 📟

O app envia comandos para a API. O ESP32 consulta a API e executa os comandos.

📚 Conceitos Novos

WiFi.h

Biblioteca do ESP32 para conectar em redes WiFi.

#include <WiFi.h>

HTTPClient.h

Biblioteca para fazer requisições HTTP (GET, POST).

#include <HTTPClient.h>

useState (React)

Hook que cria uma "variável reativa" no React - quando muda, a tela atualiza!

const [ligado, setLigado] = useState(false);

fetch (JavaScript)

Função para fazer requisições HTTP no JavaScript/React Native.

await fetch('http://api...');
1

Backend - API PHP

Editar no VS Code

Primeiro, vamos criar a API que vai intermediar a comunicação entre o app e o ESP32. Esta API salva e retorna o estado atual do LED.

📁 Onde criar: Salve este arquivo em C:\xampp\htdocs\iot\api\led.php

api/led.php (VS Code)
<?php
/**
 * API simples para controle do LED
 * Armazena o estado em um arquivo JSON
 */

// Permitir requisições de qualquer origem (CORS)
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type");
header("Content-Type: application/json");

// Responder preflight (OPTIONS)
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    http_response_code(200);
    exit();
}

// Arquivo que armazena o estado do LED
$arquivo = __DIR__ . '/led_estado.json';

// Inicializar arquivo se não existir
if (!file_exists($arquivo)) {
    file_put_contents($arquivo, json_encode(['ligado' => false]));
}

// GET: Retorna o estado atual do LED
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
    $estado = json_decode(file_get_contents($arquivo), true);
    echo json_encode([
        'success' => true,
        'ligado' => $estado['ligado'] ?? false
    ]);
    exit();
}

// POST: Altera o estado do LED
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $dados = json_decode(file_get_contents('php://input'), true);
    
    if (isset($dados['ligado'])) {
        $novoEstado = ['ligado' => (bool)$dados['ligado']];
        file_put_contents($arquivo, json_encode($novoEstado));
        
        echo json_encode([
            'success' => true,
            'message' => 'Estado atualizado!',
            'ligado' => $novoEstado['ligado']
        ]);
    } else {
        http_response_code(400);
        echo json_encode(['success' => false, 'error' => 'Campo "ligado" é obrigatório']);
    }
    exit();
}

// Método não suportado
http_response_code(405);
echo json_encode(['error' => 'Método não permitido']);
?>

CORS Headers

As linhas com Access-Control-Allow-* permitem que o app mobile (que está em outro domínio) acesse a API. Sem isso, o navegador bloqueia!

Armazenamento em JSON

Usamos um arquivo led_estado.json para simplicidade. Em produção, você usaria o banco de dados MySQL!

2

ESP32 - Código WiFi

Upload via Arduino IDE

⚠️ Atenção: Você precisa alterar o SSID e SENHA para sua rede WiFi, e o IP do servidor para o IP do seu computador!

led_mobile.ino (Arduino IDE)
/************************************************
 * LED CONTROLADO VIA MOBILE - ESP32
 * O ESP32 consulta a API a cada segundo
 * e liga/desliga o LED conforme o comando
 ***********************************************/

#include <WiFi.h>
#include <HTTPClient.h>

// ========== CONFIGURAÇÕES ==========
// ALTERE PARA SUA REDE WIFI!
const char* SSID = "NOME_DA_SUA_REDE";
const char* SENHA = "SENHA_DA_SUA_REDE";

// ALTERE PARA O IP DO SEU COMPUTADOR!
// Descubra com: ipconfig (Windows) ou ifconfig (Linux/Mac)
const char* URL_API = "http://192.168.1.100/iot/api/led.php";

// Pino do LED
const int PINO_LED = 13;

void setup() {
  Serial.begin(115200);
  
  // Configura o LED como saída
  pinMode(PINO_LED, OUTPUT);
  digitalWrite(PINO_LED, LOW);

  // Conecta ao WiFi
  Serial.println("Conectando ao WiFi...");
  WiFi.begin(SSID, SENHA);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("✅ WiFi conectado!");
  Serial.print("IP: ");
  Serial.println(WiFi.localIP());
  Serial.println("Aguardando comandos do app...");
}

void loop() {
  if (WiFi.status() == WL_CONNECTED) {
    HTTPClient http;
    
    // Faz requisição GET para a API
    http.begin(URL_API);
    int httpCode = http.GET();

    if (httpCode == 200) {
      String resposta = http.getString();
      
      // Verifica se deve ligar o LED
      if (resposta.indexOf("\"ligado\":true") > 0) {
        digitalWrite(PINO_LED, HIGH);
        Serial.println("💡 LED LIGADO");
      } else {
        digitalWrite(PINO_LED, LOW);
        Serial.println("🔌 LED DESLIGADO");
      }
    } else {
      Serial.print("Erro HTTP: ");
      Serial.println(httpCode);
    }

    http.end();
  } else {
    Serial.println("⚠️ WiFi desconectado! Reconectando...");
    WiFi.begin(SSID, SENHA);
  }

  // Consulta a cada 1 segundo
  delay(1000);
}

🔍 Como descobrir o IP do computador?

Windows

ipconfig

Procure por "IPv4 Address"

Linux/Mac

ifconfig

Procure por "inet"

3

App Mobile - Expo Snack

Programar em snack.expo.dev

Este código cria um botão grande e bonito que muda de cor quando você toca. Ao tocar, ele envia o comando para a API!

⚠️ Importante: Altere a variável URL_API para o IP do seu computador (o mesmo que você colocou no ESP32).

App.js (Expo Snack)
/**
 * App de Controle do LED - Etapa 2
 * Tutorial do Prof. Reginaldo
 */

import React, { useState, useEffect } from 'react';
import { 
  View, 
  Text, 
  TouchableOpacity, 
  StyleSheet,
  ActivityIndicator 
} from 'react-native';

// ⚠️ ALTERE PARA O IP DO SEU COMPUTADOR!
const URL_API = 'http://192.168.1.100/iot/api/led.php';

export default function App() {
  // Estado do LED (true = ligado, false = desligado)
  const [ligado, setLigado] = useState(false);
  const [carregando, setCarregando] = useState(false);
  const [erro, setErro] = useState(null);

  // Carregar estado inicial ao abrir o app
  useEffect(() => {
    carregarEstado();
  }, []);

  // Função para carregar o estado atual do LED
  const carregarEstado = async () => {
    try {
      const resposta = await fetch(URL_API);
      const dados = await resposta.json();
      setLigado(dados.ligado);
      setErro(null);
    } catch (e) {
      setErro('Erro ao conectar com a API');
    }
  };

  // Função para alternar o estado do LED
  const alternarLED = async () => {
    setCarregando(true);
    
    try {
      const novoEstado = !ligado;
      
      const resposta = await fetch(URL_API, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ ligado: novoEstado })
      });
      
      const dados = await resposta.json();
      
      if (dados.success) {
        setLigado(novoEstado);
        setErro(null);
      }
    } catch (e) {
      setErro('Erro ao enviar comando');
    }
    
    setCarregando(false);
  };

  return (
    <View style={styles.container}>
      <Text style={styles.titulo}>💡 Controle do LED</Text>
      <Text style={styles.subtitulo}>Toque para ligar/desligar</Text>
      
      <TouchableOpacity 
        style={[
          styles.botao,
          ligado ? styles.botaoLigado : styles.botaoDesligado
        ]}
        onPress={alternarLED}
        disabled={carregando}
      >
        {carregando ? (
          <ActivityIndicator size="large" color="#fff" />
        ) : (
          <>
            <Text style={styles.emoji}>
              {ligado ? '💡' : '🔌'}
            </Text>
            <Text style={styles.botaoTexto}>
              {ligado ? 'LIGADO' : 'DESLIGADO'}
            </Text>
          </>
        )}
      </TouchableOpacity>
      
      {erro && (
        <Text style={styles.erro}>⚠️ {erro}</Text>
      )}
      
      <Text style={styles.rodape}>
        Tutorial do Prof. Reginaldo
      </Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#0f172a',
    alignItems: 'center',
    justifyContent: 'center',
    padding: 20,
  },
  titulo: {
    fontSize: 28,
    fontWeight: 'bold',
    color: '#22d3ee',
    marginBottom: 8,
  },
  subtitulo: {
    fontSize: 16,
    color: '#64748b',
    marginBottom: 40,
  },
  botao: {
    width: 200,
    height: 200,
    borderRadius: 100,
    alignItems: 'center',
    justifyContent: 'center',
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 4 },
    shadowOpacity: 0.3,
    shadowRadius: 8,
    elevation: 8,
  },
  botaoLigado: {
    backgroundColor: '#22c55e', // Verde
  },
  botaoDesligado: {
    backgroundColor: '#475569', // Cinza
  },
  emoji: {
    fontSize: 60,
    marginBottom: 10,
  },
  botaoTexto: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#fff',
  },
  erro: {
    color: '#ef4444',
    marginTop: 20,
    fontSize: 14,
  },
  rodape: {
    position: 'absolute',
    bottom: 40,
    color: '#475569',
    fontSize: 12,
  },
});

useState - Estado reativo

const [ligado, setLigado] = useState(false) cria uma variável ligado e uma função setLigado para alterá-la. Quando você chama setLigado(true), a tela atualiza automaticamente!

useEffect - Executar ao carregar

O useEffect executa uma função quando o componente é carregado. Usamos para buscar o estado inicial do LED assim que o app abre.

TouchableOpacity - Botão tocável

É o "botão" do React Native. O onPress define o que acontece quando você toca. Ele tem uma animação de opacidade embutida!

🧪 Testando o Sistema Completo

  1. 1

    Inicie o XAMPP (Apache)

    A API PHP precisa estar rodando

  2. 2

    Faça upload do código no ESP32

    Abra o Monitor Serial para ver o status

  3. 3

    Cole o código no Expo Snack e teste no celular

    Use o Expo Go para escanear o QR Code

  4. 4

    Toque no botão e veja o LED acender! 🎉

    O comando vai do app → API → ESP32 → LED

🎉 Parabéns! Você criou um sistema IoT completo com controle via aplicativo mobile!

📊 Fluxo de Comunicação

📱 Usuário toca no botão do App
🌐 App envia POST para API PHP
{ "ligado": true }
💾 API salva o estado no arquivo JSON
📟 ESP32 faz GET a cada 1 segundo
💡 LED liga/desliga conforme resposta

🏆 Desafios

FÁCIL

Mude as cores

Altere as cores do botão ligado/desligado para suas favoritas.

MÉDIO

Adicione vibração

Use Vibration.vibrate() do React Native quando o botão for tocado.

📝 Teste seus Conhecimentos

🎮

Quiz Gamificado

Responda às perguntas para ganhar XP e desbloquear a próxima atividade!

Carregando quiz...