Sistema simple de control supervisado con video esp32cam

Hola, He hecho esto para el control de elementos por web y aqui lo dejo para que al que le guste el tema y quiere tener algo util pueda realizarlo.
cualquier duda me lo decis y responderé hasta donde lleguen mis conocimientos que no son muchos.


https://studio.youtube.com/video/VN5OTp13KuQ/edit
y que alguien me ayude como subir aquí un rar con todo lo necesario.
Saludosenlace al video rectificado

Moderador, link editado

Su publicacion se MUEVE a su ubicacion actual ya que es mas adecuada.

Moderador
Por favor, primero lee las normas del foro, esta todo muy bien explicado para que subas códigos.

  1. Si posteas en el foro en inglés usa ese idioma. Te acaban de mover de dicho foro a esta sección por usar lenguaje español. Entonces mucho cuidado para la próxima, queda anotado en tu historial!!
  2. Nada de .rar para subir tu proyecto.
    El repositorio permite archivos grandes, asi que no tienes restricciones.
    Lee las normas, observa otros temas para entender como se deben visualizar códigos y errores y no tendrás problema.
  3. El link de youtube no funciona. Al menos para mi. Edita eso también.

Perdon mi torpeza, no estoy muy puesto en esto de los foros

El link de youtube si funciona, yo he picado en el y me ha llevado a el sin problema.

No, no funciona

Primer link:

Link "rectificado":

podrias decirme como lo puedo arregalar?

Tenés que cambiar la configuración de privacidad, fijate en la ayuda de YouTube.

Al parecer ya funciona, que torpe soy, perdon.
enlace al video

Tampoco se si estoy en el lugar correcto.

Moderador
No funciona, sigue estando en modo edicion, al unico que le funciona es a ti.
Miralo con otro celular o pc donde no este tu cuenta registrada.

Yo si lo veo

Moderador
Sigo sin poder verlo.

Editado min mas tarde
Lo tuve que buscar como lo indico @MaximoEsfuerzo en youtube y luego edité el post#1 para indicar el link del video y ahora se ve como corresponde.

Ahora no entiendo la idea del post.
No quiero malinterpretarte pero parece que te estuvieras promocionando.
Si no es así, deberías publicar códigos, enlaces, esquemas, etc, como todo tutorial de este foro.
Y si lo haces muevo esto a Documentación, porque no hay consulta sino que muestras algo terminado.
Me parece bien pero si no hay información de como lo has hecho, pareciera tener otro significado.

No hay ninguna intencion de promocionar nada pues ya a Mis años (68) jubilado solo me queda entretenerme en estas cosas, solo mostrar algo que he hecho con mucho esfuerzo y si llevas razon en que debería haber subido más datos, se me pasó con lo de arreglar el video, destacar que Yo no estoy muy puesto en esto de subir videos, Pido disculpas por ello.
si alguien es tan amable de decirme como puedo subir un rar con todo lo necesario lo agradecería.

Enlace al rar con todo lo necesario.
https://files.fm/f/2t4a6hnjms
Espero que funcione.

Voy a intentar explicar el funcionamiento, está compuesto por dos modulos esp32cam uno de ellos configurado como solo cámara para ver video en directo, el otro esp32es el mismo modulo pero sin colocarle la cam, este aloja una web que como se ve en el video tiene botones para remotamente manejar cuatro salidas, dos en on/off que se pueden usar por ejemplo para encender y apagar luces manualmente, la tercera controla el encendio de lo conectado de forma crepuscular mediante LDR y una cuarta que activa un emisor 433 con protocolo standar para abrir una puerta automatica via radio, los modulos esp32 van conectados a wifi con ip estaticas diferentes y puertos diferentes, se puede acceder desde el interior con la ip designada y puerto, desde el exterior con la ip publica y puerto, si no se dispone de ip publica estatica se puede acceder con un ddns, por ejemplo notip (http://dominio.ddns.net:xx).
De esta forma podemos activar la apertura de una puerta atomatica viendo en directo lo que ocurre, además de controlar el estado de las salidas si están activadas o no.
Dispone de dos botones en el pcb para encender y apagar localmente las salidas, reflejandose el estado remotamente en la web.
Se puede regular el umbral de luminosidad de la LDR mediente potenciometro.
Para acceder a la web se necesita usuario y contraseña que se define en el codigo, por defecto: admin, admin

Se ve muy interesante tu proyecto.
Espero ansioso la publicación de los archivos.
Mira tu casilla privada.


// ESTE PARA EL MODULO ESP32C CON CAMARA


//hAY QUE MODIFICAR A LAS NECESIDADES LAS LINEAS SIGUIENTES
// 9 NOMBRE DE LA WIFI
// 10 CONTRASELA WIFI
// 34 PUERTO NECESARIO, DIFERENTE AL DEL OTRO MODULO ESP32 
// 134 IP ESTATICA DESEADA, DIFERENTE AL OTRO MODULO
// 135 PUERTA DE ENLACE DESEADA
// SI SE NECESITA ACCEDER DESDE FUERA HAY QUE ABRIR LOS PUERTOS CORRESPONDIENTES

#include "esp_camera.h"
#include <WiFi.h>
#include <WebServer.h>

const char* ssid = "NOMBRE DE LA WIFI";QUÍ HAY QUE PONER EL NOMBRE DE LA WIFO 
const char* password = "CONTRASEÑA DE LA WIFI"; AQUÍ HAY QUE PONER LA CONTRASEÑA DE LA WIFI

#define PWDN_GPIO_NUM     32
#define RESET_GPIO_NUM    -1
#define XCLK_GPIO_NUM      0
#define SIOD_GPIO_NUM     26
#define SIOC_GPIO_NUM     27
#define Y9_GPIO_NUM       35
#define Y8_GPIO_NUM       34
#define Y7_GPIO_NUM       39
#define Y6_GPIO_NUM       36
#define Y5_GPIO_NUM       21
#define Y4_GPIO_NUM       19
#define Y3_GPIO_NUM       18
#define Y2_GPIO_NUM        5
#define VSYNC_GPIO_NUM    25
#define HREF_GPIO_NUM     23
#define PCLK_GPIO_NUM     22
#define WIFI_LED_PIN       4 // GPIO4 para el LED de estado de Wi-Fi

WebServer server(88);AQUÍ HAY QUE PONER EL PUERTO DESEADO

void handle_root() {
  String html = "<html><head><title>LOS PAREJOS</title></head><body>";
 
  html += "<style>";
  html += "  .video-container {";
  html += "    display: flex;";
  html += "    justify-content: center;";
  html += "    align-items: flex-start;";
  html += "    height: 100vh;";
  html += "  }";
  html += "  .video-iframe {";
  html += "    width: 1080px;";
  html += "    height: 720px;";
  html += "    border: none;";
  html += "  }";
  html += "</style>";

  html += "<div class='video-container'>";
  html += "<iframe class='video-iframe' src=\"/stream\"></iframe>";
  html += "</div>";

  server.send(200, "text/html", html);
}

void handle_jpg_stream() {
  WiFiClient client = server.client();
  camera_fb_t * fb = NULL;
  String response = "HTTP/1.1 200 OK\r\n";
  response += "Content-Type: multipart/x-mixed-replace; boundary=frame\r\n\r\n";
  client.print(response);

  while (client.connected()) {
    fb = esp_camera_fb_get();
    if (!fb) {
      Serial.println("Camera capture failed");
      return;
    }
    response = "--frame\r\n";
    response += "Content-Type: image/jpeg\r\n\r\n";
    client.print(response);

    client.write(fb->buf, fb->len);
    client.print("\r\n");
    esp_camera_fb_return(fb);

    delay(10);
  }
}

void startCameraServer() {
  server.on("/", HTTP_GET, handle_root);
  server.on("/stream", HTTP_GET, handle_jpg_stream);
  server.begin();
}

void wifiLedBlink(int times, int delayTime, int ledPin) {
  for (int i = 0; i < times; i++) {
    digitalWrite(ledPin, HIGH);
    delay(delayTime);
    digitalWrite(ledPin, LOW);
    delay(delayTime);
  }
}

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

  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG; 
  config.frame_size = FRAMESIZE_SVGA;
  config.jpeg_quality = 12;
  config.fb_count = 1;

  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    return;
  }

  IPAddress staticIP(192, 168, 10, 191);
  IPAddress gateway(192, 168, 10, 1);
  IPAddress subnet(255, 255, 255, 0);
  IPAddress primaryDNS(8, 8, 8, 8);
  IPAddress secondaryDNS(8, 8, 4, 4);

  if (!WiFi.config(staticIP, gateway, subnet, primaryDNS, secondaryDNS)) {
    Serial.println("Failed to configure static IP");
  }

  pinMode(WIFI_LED_PIN, OUTPUT);

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    wifiLedBlink(5, 300, WIFI_LED_PIN); // Parpadeo rápido al intentar conectarse
    delay(500);
    Serial.print(".");
  }
  wifiLedBlink(5, 300, WIFI_LED_PIN); // Parpadeo rápido cuando se conecta
  Serial.println("");
  Serial.println("WiFi connected");

  // Iniciar el servidor web
  startCameraServer();

  Serial.print("Camera Stream Ready! Go to: http://");
  Serial.print(staticIP);
  Serial.print(":");
  Serial.print(88);
}

void loop() {
  server.handleClient();

  // Parpadeo lento cuando no está conectado a Wi-Fi
  if (WiFi.status() != WL_CONNECTED) {
    wifiLedBlink(1, 1000, WIFI_LED_PIN); // Parpadeo lento cuando no está conectado
  }
}



//ESTE PARA EL MODULO ESP32C SIN CÁMARA

// HAY QUE MODIFICAR A LAS NECESIDADES LAS SIGUIENTES LINEAS
// 18 NOMBRE DE LA WIFI
// 19 CONTRASEÑA WIFI
// 22 NOMBRE USUARIO PARA ACCEDER A LA WEB(admin)
// 23 CONTRASEÑA PARA ACCEDER A LA WEB (admin)
// 25 IP FIJA DESEADA, DIFRENTE AL OTRO MODULO
// 26 PUERTA DE ENLACE NECESARIA
// 35 PUERTO DESEADO, DIFRENTE AL OTRO MODULO
// 144 IP:PUERTO ASIGNADA AL OTRO MODULO, IP PUBLICA:PUERTO, EN EL CASO DE IP DINAMICA USAR NOTIP, EJEMPLO, (visionremota.ddns.net:88)
// TODO ES ADACTABLE A LAS NECESIDADES DE CADA UNO 





#include <Arduino.h>
#include <WiFi.h>
#include <ESPAsyncWebSrv.h>

const char* ssid = "NOMBRE DE LA WIFI";           // Nombre de tu red WiFi
const char* password = "CONTRASEÑA";      // Contraseña de tu red WiFi
const char* webUsername = "admin";    // Nombre de usuario para la autenticación
const char* webPassword = "admin"; // Contraseña para la autenticación

const IPAddress staticIP(192, 168, 1, 190); // IP estática
const IPAddress gateway(192, 168, 1, 1);   // Puerta de enlace
const IPAddress subnet(255, 255, 255, 0);  // Máscara de subred

const int gpio12Pin = 12; // GPIO12 para control on/off
const int gpio13Pin = 13; // GPIO13 para control on/off
const int gpio14Pin = 14; // GPIO14 para control momentáneo (2 segundos)
const int gpio15Pin = 15; // GPIO15 para control on/off
const int gpio0Pin = 0;   // GPIO0 para el pulsador placa
const int gpio16Pin = 16; // GPIO16 para el pulsador placa
const int gpio2Pin = 2;   // GPIO2 para la entrada LDR
const int wifiStatusPin = 4; // GPIO4 para indicar el estado de WiFi

AsyncWebServer server(85);

bool stateGPIO12 = LOW;
bool stateGPIO13 = LOW;
bool stateGPIO14 = LOW;
bool stateGPIO15 = LOW;

void toggleGPIO(int pin) {
  if (pin == 12) {
    stateGPIO12 = !stateGPIO12;
    digitalWrite(gpio12Pin, stateGPIO12);
  } else if (pin == 13) {
    stateGPIO13 = !stateGPIO13;
    digitalWrite(gpio13Pin, stateGPIO13);
  } else if (pin == 15) {
    stateGPIO15 = !stateGPIO15;
    digitalWrite(gpio15Pin, stateGPIO15);
  }
}

String getButtonStateScript() {
  String script = "document.getElementById('gpio13state').textContent = '" + String(stateGPIO13 ? "Encendido" : "Apagado") + "';";
  script += "document.getElementById('gpio14state').textContent = '" + String(stateGPIO14 ? "Encendido" : "Apagado") + "';";
  script += "document.getElementById('gpio15state').textContent = '" + String(stateGPIO15 ? "Encendido" : "Apagado") + "';";
  return script;
}

String getStatusJson() {
  String status = "{\"gpio12\":" + String(stateGPIO12) + ",\"gpio13\":" + String(stateGPIO13) + ",\"gpio14\":" + String(stateGPIO14) + ",\"gpio15\":" + String(stateGPIO15) + "}";
  return status;
}

void setup() {
  digitalWrite(gpio14Pin, HIGH); // INICIA EL GPIO14 EN NIVEL ALTO

  Serial.begin(115200);

  // Configura la conexión WiFi con IP estática
  WiFi.config(staticIP, gateway, subnet);
  WiFi.begin(ssid, password);

  pinMode(wifiStatusPin, OUTPUT); // Configura el pin de estado de WiFi
  digitalWrite(wifiStatusPin, LOW); // Inicialmente, apagar el LED

  pinMode(gpio12Pin, OUTPUT);
  pinMode(gpio13Pin, OUTPUT);
  pinMode(gpio14Pin, OUTPUT);
  pinMode(gpio15Pin, OUTPUT);
  pinMode(gpio0Pin, INPUT_PULLUP); // Configura GPIO0 como entrada con resistencia de pull-up
  pinMode(gpio16Pin, INPUT_PULLUP); // Configura GPIO16 como entrada con resistencia de pull-up
  pinMode(gpio2Pin, INPUT); // Configura GPIO2 como entrada

  while (WiFi.status() != WL_CONNECTED) {
    digitalWrite(wifiStatusPin, !digitalRead(wifiStatusPin)); // Cambia el estado del LED
    delay(200);
    Serial.println("Conectando a WiFi...");
  }
  Serial.println("Conexión WiFi establecida");
  digitalWrite(wifiStatusPin, LOW); // Apaga el LED de estado de WiFi

  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
    // Verifica la autenticación antes de permitir el acceso
    if (!request->authenticate(webUsername, webPassword)) {
      return request->requestAuthentication();
    }

    String html = "<html><head>";
    html += "<style>";
    html += ".container {";
    html += "  display: flex;";
    html += "  flex-direction: column;";
    html += "  align-items: center;";
    html += "  justify-content: flex-start;"; // Centrar la ventana de video arriba
    html += "  height: 100vh;";
    html += "  margin: 0;";
    html += "  padding: 0;";
    html += "  overflow: hidden;";
    html += "}";
    html += ".button {";
    html += "  background-color: #4CAF50;";
    html += "  border: none;";
    html += "  color: white;";
    html += "  padding: 30px 60px;";
    html += "  text-align: center;";
    html += "  text-decoration: none;";
    html += "  display: inline-block;";
    html += "  font-size: 36px;";
    html += "  margin: 20px;";
    html += "  cursor: pointer;";
    html += "  border-radius: 10px;"; // Agregar borde redondeado
    html += "}";
    html += ".button.clicked {";
    html += "  background-color: #45a049;";
    html += "}";
    html += ".text-large {"; // Estilo para el texto fuera de los botones
    html += "  font-size: 48px;"; // Aumenta el tamaño de la fuente
    html += "}";
    html += ".footer {";
    html += "  font-size: 16px;";
    html += "  color: green;";
    html += "  margin-top: 20px;";
    html += "}";
    html += "</style>";
    html += "</head><body>";

    html += "<div class='container'>";
    html += "<iframe width='80%' height='40%' src='http://IP o URL DEL MODULO CON CAMARA:88' frameborder='0'></iframe>"; // Ventana de video
    html += "<span class='text-large'>PASILLO: <span id='gpio13state'></span></span>";
    html += "<button class='button' id='gpio13' onclick='toggleGPIO(13)'>ON/OFF</button><br>";

   
    html += "<span class='text-large'>ENTRADA: <span id='gpio15state'></span></span>";
    html += "<button class='button' id='gpio15' onclick='toggleGPIO(15)'>ON/OFF</button><br><br>";

     html += "<span class='text-large'>CANCELA: <span id='gpio14state'></span></span>";
     html += "<button class='button' id='gpio14' onclick='momentaryGPIO(14)'>ABRE CANCELA</button><br>";

    // Agregar el estado de gpio12 en tiempo real
    html += "<div class='text-large' style='text-align:center;'>LETRERO: <span id='gpio12state'></span></div>";

    html += "<div class='footer'>Power By A. Del Rio Para Los Parejos</div>"; // Agrega el pie de página
    html += "</div>";

    html += "<script>";
    html += getButtonStateScript();
    html += "function toggleGPIO(pin) {";
    html += "  fetch('/toggle?pin=' + pin)";
    html += "    .then(response => response.text())";
    html += "    .then(data => {";
    html += "      document.getElementById('gpio' + pin + 'state').textContent = data;";
    html += "      document.getElementById('gpio' + pin).classList.add('clicked');"; // Agrega la clase 'clicked'
    html += "      setTimeout(() => {";
    html += "        document.getElementById('gpio' + pin).classList.remove('clicked');"; // Quita la clase 'clicked' después de 200 ms
    html += "      }, 200);";
    html += "    });";
    html += "}";
    html += "function momentaryGPIO(pin) {";
    html += "  fetch('/momentary?pin=' + pin)";
    html += "    .then(response => response.text())";
    html += "    .then(data => {";
    html += "      document.getElementById('gpio' + pin + 'state').textContent = data;";
    html += "      document.getElementById('gpio' + pin).classList.add('clicked');"; // Agrega la clase 'clicked'
    html += "      setTimeout(() => {";
    html += "        document.getElementById('gpio' + pin).classList.remove('clicked');"; // Quita la clase 'clicked' después de 200 ms
    html += "      }, 200);";
    html += "    });";
    html += "}";
    html += "setInterval(updateStates, 5000);";
    html += "function updateStates() {";
    html += "  fetch('/status')";
    html += "    .then(response => response.json())";
    html += "    .then(data => {";
    html += "      document.getElementById('gpio13state').textContent = data.gpio13 ? 'ENCENDIDO' : 'Apagado';";
    html += "      document.getElementById('gpio14state').textContent = data.gpio14 ? 'cerrada' : 'ABRIENDO';";
    html += "      document.getElementById('gpio15state').textContent = data.gpio15 ? 'ENCENDIDO' : 'Apagado';";
    html += "      document.getElementById('gpio12state').textContent = data.gpio12 ? 'ENCENDIDO' : 'Apagado';"; // Actualiza el estado de GPIO12
    html += "    });";
    html += "}";
    html += "</script>";
    html += "</body></html>";
    request->send(200, "text/html", html);
  });

  server.on("/toggle", HTTP_GET, [](AsyncWebServerRequest *request) {
    String pinStr = request->arg("pin");
    int pin = pinStr.toInt();
    toggleGPIO(pin);
    request->send(200, "text/plain", String(pin));
  });

  server.on("/momentary", HTTP_GET, [](AsyncWebServerRequest *request) {
    String pinStr = request->arg("pin");
    int pin = pinStr.toInt();
    if (pin == 14) {
      digitalWrite(gpio14Pin, HIGH);
      stateGPIO14 = HIGH;
      digitalWrite(gpio14Pin, LOW);
      delay(1000);
      digitalWrite(gpio14Pin, HIGH);
      request->send(200, "text/plain", "ABRIENDO");
      delay(3000);
    }
  });

  server.on("/status", HTTP_GET, [](AsyncWebServerRequest *request) {
    String status = getStatusJson();
    request->send(200, "application/json", status);
  });

  server.begin();
}

void loop() {
  int buttonState = digitalRead(gpio0Pin);
  if (buttonState == LOW) {
    toggleGPIO(15);
    delay(500);
  }

  int buttonState16 = digitalRead(gpio16Pin);
  if (buttonState16 == LOW) {
    toggleGPIO(13);
    delay(500);
  }

  int gpio2State = digitalRead(gpio2Pin);
  if (gpio2State == LOW) {
    digitalWrite(gpio12Pin, LOW);
    stateGPIO12 = LOW;
  } else {
    digitalWrite(gpio12Pin, HIGH);
    stateGPIO12 = HIGH;
  }

  if (WiFi.status() != WL_CONNECTED) {
    digitalWrite(wifiStatusPin, !digitalRead(wifiStatusPin));
    delay(200);
  }
}