Comunicacion entre microcontroladores ESP32-CAM y ESP32Wrover-E

Buenos días. Tengo un problema al intentar integrar el código fuente que he desarrollado en un entorno de prueba, donde funciona como maestro (ESP32-CAM), en mi proyecto principal de reconocimiento facial. Este maestro debería enviar datos de tipo entero al esclavo (ESP32-Wrover-E) para activar un LED, utilizando valores como 0 o 1. Utilizando el protocolo ESP-NOW.
Código fuente maestro ESP32-CAM - entorno de prueba:

#include <esp_now.h>
#include <WiFi.h>
  // REEMPLAZAR CON LA dirección MAC DE SU EMISOR
  // ESP32WROVER-DEV (EMISOR) = 31:AE:A5:64:B8:30
 uint8_t broadcastAddress[] = {0x31, 0xAE, 0xA5, 0x64, 0xB8, 0x30};

  // Ejemplo de estructura para enviar datos
  // Debe coincidir con la estructura del receptor
  typedef struct struct_message
  {
    int a;
  } struct_message;

  // Crea una estructura_message llamada myData
  //struct_message myDataRec;
  struct_message myDataSen;

esp_now_peer_info_t peerInfo;



// devolución de llamada cuando se envían datos
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status)
{
 Serial.print("\r\nEstado del último envío de paquete:\t");
 Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Éxito de entrega" : "Fallo de entrega");
}  



void setup()
{
  // Iniciar el monitor serie
  Serial.begin(115200);

  // Configurar el dispositivo como estación Wi-Fi (modo static)
  WiFi.mode(WIFI_STA);

  // Inicia el protocolo ESP-NOW
  if(esp_now_init() != ESP_OK)
  {
    Serial.println("Error al inicializar ESP-NOW");
    return;
  }

  // Una vez que ESP-NOW se inicie exitosamente, nos registraremos para enviar CB a
  // Obtener el estado del paquete transmitido
  esp_now_register_send_cb(OnDataSent);

  // Registrar par (esclavo)
  memcpy(peerInfo.peer_addr, broadcastAddress, 6); // dirección MAC del receptor
  peerInfo.channel = 0;     // canal la cual va trasmitir, por defecto 0
  peerInfo.encrypt = false; 

  // Agregar par (esclavo)
  if(esp_now_add_peer(&peerInfo) != ESP_OK)
  {
    Serial.println("No se pudo agregar par");
    return;
  }

  
}


void loop()
{

 if(Serial.available())
 {
   char letra = Serial.read();
   if(letra == 'E')
   {
     // Establecer valores para enviar
      myDataSen.a = 1;

      // Enviar mensaje vía ESP-NOW
      esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &myDataSen, sizeof(myDataSen));

      if(result == ESP_OK)
      {
        Serial.println("Enviado con éxito");
      }else
      {
        Serial.println("Error al enviar los datos");
      }
   }
 } 
 	
  
}

Código fuente esclavo ESP32Wrover-E - entorno de prueba:

#include <esp_now.h>
#include <WiFi.h>
  
  // Ejemplo de estructura para recibir datos
  // Debe coincidir con la estructura del remitente
  typedef struct struct_message
  {
    int a;
  } struct_message;

  // Crea una estructura_message llamada myData
  struct_message myDataRec;
  
 // esp_now_peer_info_t peerInfo;

  //Definir led pin del ESPWrover
  #define LED_BUILTIN 2  


// función de devolución de llamada que se ejecutará cuando se reciban datos
void OnDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len)
{
  memcpy(&myDataRec, incomingData, sizeof(myDataRec));
  Serial.print("Bytes recibidos: ");
  Serial.println(len);
  Serial.print("Int: ");
  Serial.println(myDataRec.a);
  Serial.println();
 

  if(myDataRec.a == 1)
  {
    digitalWrite(LED_BUILTIN, HIGH); //encender led
    delay(2000);
    digitalWrite(LED_BUILTIN, LOW); //apagar led
  }
  
  
}

void setup()
{
  // Inicializar monitor serie 
  Serial.begin(115200);
  // Configurar el dispositivo como estación Wi-Fi (modo static)
  WiFi.mode(WIFI_STA);

  digitalWrite(LED_BUILTIN, LOW);
  //configuracion del led del pin2
  pinMode(LED_BUILTIN, OUTPUT);
  

  //Iinica el protocolo ESP-NOW
  if(esp_now_init() != ESP_OK)
  {
    Serial.println("Error al inicializar ESP-NOW");
    return;
  } 

  // Una vez que ESPNow haya iniciado exitosamente, nos registraremos para recibir CB para 
  // Obtener información del empaquetador recv
  esp_now_register_recv_cb(OnDataRecv);
  
    
}


void loop()
{
  
  

}

Estos dos dispositivos se comunican perfectamente entre sí en ambiente de prueba; no hay ningún problema en esa parte. Sin embargo, mi dificultad surge al intentar agregar el mismo código al programa principal en el que uno de ellos actúa como maestro y envía datos al otro como esclavo. ¿Podrían ayudarme con esto? Llevo varios días intentándolo y ya no sé qué más hacer. Aunque este protocolo es excelente, parece que hay algún detalle que falta ajustar, ya que no está funcionando como esperaba.

Intenté agregar todo lo que hice en el ambiente de prueba del dispositivo que hace el papel de maestro, en mi código principal, que es esto:

// Se incluyen las bibliotecas a utilizar:
#include <ArduinoWebsockets.h>  
#include "esp_http_server.h"
#include "esp_timer.h"
#include "esp_camera.h"
#include "camera_index.h"
#include "Arduino.h"
#include "fd_forward.h"
#include "fr_forward.h"
#include "fr_flash.h"
#include <ESPmDNS.h>  // Biblioteca para el multicast DNS (mDNS)

const char* ssid = "prueba";          // Identificador de la red
const char* password = "12345678";    // Password de la red

#define ENROLL_CONFIRM_TIMES 5    // Realiza la toma de la cara en 5 minutos ante de guardar, por esa razon el valor de "5"
#define FACE_ID_SAVE_NUMBER  7    // Guarda hasta 7 id de caras en la base de datos o memoria flash del esp, por esa razon el valor "7"
#define LED_FLASH 4               // Uitliza 4 para el pin del LED flash 
bool flash_led_on = false;        // El flash del led permanene en modo desligado o falso        

#define CAMERA_MODEL_AI_THINKER   // Se define el modelo de camara que está configurado en el archivo camera_pins.h
#include "camera_pins.h"          // Biblioteca utilizada para la configuracion de las camaras

using namespace websockets;
WebsocketsServer socket_server;

camera_fb_t * fb = NULL;

long current_millis;                    // Milisengundos actuales
long last_detected_millis = 0;          // Ultimos milisegundos detectados

#define relay_pin 2                     // Utiliza el pin 2 para el relay (el pin 12 también se puede utilizar)
unsigned long door_opened_millis = 0;   // Puerta abierta en milisegundos 
long interval = 5000;                   // Abrir la cerradura durante el intervalo de .. milisegundos, luego cierra
bool face_recognised = false;           // Rostro reconocido es igual a falso 

void app_facenet_main();                // Llama la funcion app_facenet_main
void app_httpserver_init();             // Llama la funcion app_httpserver_init

// Estructura que almacena todas las informaciones que contiene la cara:
typedef struct
{
  uint8_t *image;           
  box_array_t *net_boxes;   
  dl_matrix3d_t *face_id;
} http_img_process_result;


static inline mtmn_config_t app_mtmn_config()
{
  mtmn_config_t mtmn_config = {0};
  mtmn_config.type = FAST;
  mtmn_config.min_face = 80;
  mtmn_config.pyramid = 0.707;
  mtmn_config.pyramid_times = 4;
  mtmn_config.p_threshold.score = 0.6;
  mtmn_config.p_threshold.nms = 0.7;
  mtmn_config.p_threshold.candidate_number = 20;
  mtmn_config.r_threshold.score = 0.7;
  mtmn_config.r_threshold.nms = 0.7;
  mtmn_config.r_threshold.candidate_number = 10;
  mtmn_config.o_threshold.score = 0.7;
  mtmn_config.o_threshold.nms = 0.7;
  mtmn_config.o_threshold.candidate_number = 1;
  return mtmn_config;
}
mtmn_config_t mtmn_config = app_mtmn_config();

face_id_name_list st_face_list;
static dl_matrix3du_t *aligned_face = NULL;

httpd_handle_t camera_httpd = NULL;

// Estructura 
typedef enum
{
  START_STREAM,   
  SHOW_FACES,
  START_RECOGNITION,
  START_ENROLL,
  ENROLL_COMPLETE,
  DELETE_ALL,
} en_fsm_state;
en_fsm_state g_state;

typedef struct
{
  char enroll_name[ENROLL_NAME_LEN];
} httpd_resp_value;

httpd_resp_value st_name;

void setup()
{

  Serial.begin(115200);
  Serial.setDebugOutput(true);
  Serial.println();

  digitalWrite(relay_pin, LOW);
  pinMode(relay_pin, OUTPUT);
  digitalWrite(LED_FLASH, LOW);   // Pin del flash del esp permanece apagado
  pinMode(LED_FLASH, OUTPUT);     // Pin para el flash del esp de tipo salida

  // Configuracion de la camara
  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;

  //inicia con especificaciones altas para preasignar buffers más grandes
  if (psramFound()) {                     // Si PSRAM está presente, entonces hace lo siguiente:
    config.frame_size = FRAMESIZE_UXGA;   // Se configura el tamaño de cuadro (frame size) de la cámara a UXGA.
    config.jpeg_quality = 10;             // Se establece la calidad JPEG en 10.
    config.fb_count = 2;                  // Se establece el recuento (count) de búferes de framebuffer en 2.
  } else {                                // Si PSRAM no está presente, entonces hace lo siguiente:
    config.frame_size = FRAMESIZE_SVGA;   // Se configura el tamaño de cuadro de la cámara a SVGA.
    config.jpeg_quality = 12;             // Se establece la calidad JPEG en 12.
    config.fb_count = 1;                  // Se establece el recuento de búferes de framebuffer en 1.
  }


  // Inicio de cámara
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {      // Si err no es igual a ESP_OK, entonces hace lo siguiente:
    Serial.printf("El inicio de la cámara falló con error 0x%x", err);  //  Indica que ha ocurrido un error durante la inicialización de la cámara
    esp_restart(); // Reiniciar el ESP32
    return;                 // Después de imprimir el mensaje de error, la función devuelve el control inmediatamente. 
  }

  sensor_t * s = esp_camera_sensor_get();
  s->set_framesize(s, FRAMESIZE_QVGA);

  // Inicializar mDNS
  if (!MDNS.begin("servidor")) {
    Serial.println("Error al iniciar el servicio mDNS");
  } else {
    Serial.println("mDNS iniciado correctamente");
  }
  // Conectar a WiFi
  conexion_wifi();
 
  // Anunciar el servicio mDNS
  if (MDNS.begin("servidor")) {
    Serial.println("mDNS servicio anunciado");
    Serial.print("Camara Lista! Usar: 'http://");
    Serial.print("servidor.local");
    Serial.println(" para conectar e iniciar el reconocimiento facial");
  } else {
    Serial.println("Error al anunciar el servicio mDNS");
  }

  app_httpserver_init();
  app_facenet_main();
  socket_server.listen(82);

  Serial.print("Camara Lista! Usar: 'http://");
  Serial.print(WiFi.localIP());
  Serial.println(" para conectar e iniciar el reconocimiento facial");
}

static esp_err_t index_handler(httpd_req_t *req)
{
  httpd_resp_set_type(req, "text/html");
  httpd_resp_set_hdr(req, "Content-Encoding", "gzip");
  return httpd_resp_send(req, (const char *)index_ov2640_html_gz, index_ov2640_html_gz_len);
}

httpd_uri_t index_uri = 
{
  .uri       = "/",
  .method    = HTTP_GET,
  .handler   = index_handler,
  .user_ctx  = NULL
};

void app_httpserver_init ()
{
  httpd_config_t config = HTTPD_DEFAULT_CONFIG();

  // NUEVO: PARA mDNS
  // Configurar el nombre del host
  config.server_port = 80;  // Puerto HTTP estándar

  if (httpd_start(&camera_httpd, &config) == ESP_OK)
    Serial.println("httpd_start");
  //{
    httpd_register_uri_handler(camera_httpd, &index_uri);
  //}
}

void app_facenet_main()
{
  face_id_name_init(&st_face_list, FACE_ID_SAVE_NUMBER, ENROLL_CONFIRM_TIMES);
  aligned_face = dl_matrix3du_alloc(1, FACE_WIDTH, FACE_HEIGHT, 3);
  read_face_id_from_flash_with_name(&st_face_list);
}     

// REGISTRA NUEVO ROSTRO EN EL ESP32CAM:
static inline int do_enrollment(face_id_name_list *face_list, dl_matrix3d_t *new_id)  
{
  ESP_LOGD(TAG, "START ENROLLING");
  int left_sample_face = enroll_face_id_to_flash_with_name(face_list, new_id, st_name.enroll_name);
  ESP_LOGD(TAG, "Face ID %s Enrollment: Sample %d",
           st_name.enroll_name,
           ENROLL_CONFIRM_TIMES - left_sample_face);
  return left_sample_face;
}
// UTILIZA EL WEBSOCKETS PARA ENVIAR LISTAS DE LAS CARAS:
static esp_err_t send_face_list(WebsocketsClient &client)
{
  client.send("delete_faces");                         // Envia un mensaje al cliente, indicando al navegador que borre todas las caras 
  face_id_node *head = st_face_list.head;
  char add_face[64];
  for (int i = 0; i < st_face_list.count; i++)         // bucle de caras actuales
  {
    sprintf(add_face, "listface:%s", head->id_name);  
    client.send(add_face);                             // enviar cara al navegador
    head = head->next;
  }
}
// UTILIZA PARA ELIMINAR LAS CARAS DEL ESP32CAM Y DEL NAVEGADOR:
static esp_err_t delete_all_faces(WebsocketsClient &client)
{
  delete_face_all_in_flash_with_name(&st_face_list);
  client.send("delete_faces");
}

void handle_message(WebsocketsClient &client, WebsocketsMessage msg)
{
  if (msg.data() == "stream")
  {   // Verifica si el mensaje recibido a través de WebSocket es igual a la cadena "stream"
    g_state = START_STREAM;
    client.send("TRANSMISIÓN");
  }
  if (msg.data().substring(0, 8) == "capture:")
  {
    g_state = START_ENROLL;
    char person[FACE_ID_SAVE_NUMBER * ENROLL_NAME_LEN] = {0,};  
    msg.data().substring(8).toCharArray(person, sizeof(person));
    memcpy(st_name.enroll_name, person, strlen(person) + 1);
    client.send("CAPTURANDO..");
  }

  if (msg.data() == "recognise")
  {
    g_state = START_RECOGNITION;
    client.send("RECONOCIENDO..");
  }

  if (msg.data().substring(0, 7) == "remove:")
  {
    char person[ENROLL_NAME_LEN * FACE_ID_SAVE_NUMBER];
    msg.data().substring(7).toCharArray(person, sizeof(person));
    delete_face_id_in_flash_with_name(&st_face_list, person);
    //send_face_list(client); // restablecer caras en el navegador
  }
  if (msg.data() == "delete_all")
  {
    delete_all_faces(client);
  }

  if (msg.data() == "consult_faces")
  {
    send_face_list(client); // Envia la lista de caras al cliente
  }

}


void open_door(WebsocketsClient &client)
{
  if (digitalRead(relay_pin) == LOW) {      // Realiza la verificacion: Si lee el pin del relay en bajo, entonces hace lo siguiente:
    digitalWrite(relay_pin, HIGH);          // cierra (energiza) el relé para que la puerta se desbloquee
    Serial.println("Puerta desbloqueada");  
    client.send("door_open");               // Se envía un mensaje a través del cliente WebSocket (client) para notificar que la puerta se ha desbloqueado
    door_opened_millis = millis();          // Tiempo relé cerrado y puerta abierta
  }
}

//FUNCION PRINCIPAL
void loop()
{
   manejar_interaccion();
}


// FUNCIONES INDEPENDIENTES
void manejar_interaccion()
{
    auto client = socket_server.accept();       // Acepta una conexión de cliente.
    client.onMessage(handle_message);           // Configura un manejador para mensajes del cliente.
    dl_matrix3du_t *image_matrix = dl_matrix3du_alloc(1, 320, 240, 3);  // Asigna memoria para una matriz de imagen.
    http_img_process_result out_res = {0};      // Inicializa una estructura para procesar imágenes.
    out_res.image = image_matrix->item;         // Asigna la imagen procesada a la estructura.

    client.send("TRANSMITIENDO...");                 // Envía el mensaje "TRANSMITIENDO....." al cliente.

    while (client.available()) //Verifica si hay datos disponibles para ser procesados en la conexión de red establecida por el dispositivo.
    { 
      client.poll();
      // Comprueba si ha pasado un cierto tiempo desde que se abrió la puerta:
      if (millis() - interval > door_opened_millis) // hora actual - tiempo de reconocimiento facial > 5 segundos
      { 
        digitalWrite(relay_pin, LOW);  // Abre la puerta (baja el pin de relé).
      }

      fb = esp_camera_fb_get();   // Se obtiene un fotograma de la cámara utilizando "esp_camera_fb_get()"
      // Realiza diferentes acciones dependiendo del estado actual (detección, enrolamiento o reconocimiento):
      if (g_state == START_ENROLL || g_state == START_RECOGNITION) // Si g_state es igual a enrolamiento o reconocimiento, hace lo siguiente:
      {
        // Control de la LED flash del esp32cam:
        if (!flash_led_on) 
        {
          digitalWrite(LED_FLASH, HIGH); // Si la LED flash está apagada, enciende 
          flash_led_on = true;           // cambia a estado : verdadero 
        }
        // Inicializa algunas variables.
        out_res.net_boxes = NULL;
        out_res.face_id = NULL;
        // Convierte el formato del fotograma y detecta caras en la imagen.
        fmt2rgb888(fb->buf, fb->len, fb->format, out_res.image);
        out_res.net_boxes = face_detect(image_matrix, &mtmn_config);

        if (out_res.net_boxes)
        {
          // Si se detecta al menos una cara:
          if (align_face(out_res.net_boxes, image_matrix, aligned_face) == ESP_OK)
          {
            // Alinea la cara detectada y obtiene su identificación.    
            out_res.face_id = get_face_id(aligned_face);
            last_detected_millis = millis();
            // Realiza el enrolamiento de la cara.
            if (g_state == START_ENROLL)
            {
              int left_sample_face = do_enrollment(&st_face_list, out_res.face_id);
              char enrolling_message[64];
              sprintf(enrolling_message, "NUMERO DE MUESTRA %d PARA %s", ENROLL_CONFIRM_TIMES - left_sample_face, st_name.enroll_name);
              client.send(enrolling_message);
              if (left_sample_face == 0)
              {
                ESP_LOGI(TAG, "Enrolled Face ID: %s", st_face_list.tail->id_name);
                g_state = START_STREAM;
                char captured_message[64];
                sprintf(captured_message, "Rostro capturado para: %s", st_face_list.tail->id_name);
                client.send(captured_message);
                send_face_list(client);
              }
            } // Hasta acá el enrollamiento de caras
                  // Realiza el reconocimiento facial y actúa en consecuencia (abrir la puerta, etc.).
                  if (g_state == START_RECOGNITION  && (st_face_list.count > 0))
                  {
                      face_id_node *f = recognize_face_with_name(&st_face_list, out_res.face_id);
                      if (f)
                      {
                        char recognised_message[64];
                        sprintf(recognised_message, "Puerta abierta para: %s", f->id_name);
                        open_door(client);
                        digitalWrite(LED_FLASH, LOW);     // Apaga la LED flash
                        flash_led_on = false;             // Actualiza el estado del flash
                        client.send(recognised_message);
                        g_state = START_STREAM;           // Cambia el estado a "START_STREAM" para finalizar el modo "Reconocimiento"
                      }
                      else
                      {
                        client.send("CARA NO RECONOCIDA");
                      
                      }
                  }
                  dl_matrix3d_free(out_res.face_id);
              
   
          }
        }
                    
      }else
      {
        if (flash_led_on) 
          {
              digitalWrite(LED_FLASH, LOW); // Apaga la LED flash 
              flash_led_on = false;         // cambia a estado : falso
          }
      }

      client.sendBinary((const char *)fb->buf, fb->len);    // Envía el fotograma al cliente en formato binario.

      esp_camera_fb_return(fb);                              // Libera el fotograma.
      fb = NULL;
    }
    // Detener mDNS cuando no lo necesitas
    MDNS.end();
  
}


void conexion_wifi() 
{
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    //Serial.print(".");
  }
    // Serial.println("");
    // Serial.println("WiFi conectada");
}

Una vez que reconozca el rostro y abra la puerta, necesito que al menos me envíe un dato y encienda el LED en el dispositivo que hace el papel de esclavo, en este caso el ESP32Wrover.

Seré reiterativo.
Cuando los pruebas solo con ESP-NOW enviando la acción desde uno y recibiendo con el otro todo ok.
Cuando agregas todo el código de reconocimiento facial, falla, es así?

Si señor, así mismo, me aparece el error de "Fallo de entrega" en el monitor serial, es decir que el maestro no llega a comunicarse con esclavo?

Ok.. tengo la ESP32-CAM y tengo Wemos y ESP32 asi que veré de probarlos con mi MAC claro está. A ver donde se da el problema. Veo que es uno de esos casos raros.
A priori creo que algo de los recursos de ESP-NOW esta en conflicto con lo que estas usando... no se que pero recuerdo que habia una cuestión a tener en cuenta.

Entiendo. En verdad es muy extraño, deberia de funcionar, integré al código con mucho cuidado y no cambié absolutamente nada, debería de funcionar.

Ayer probé y funcionó la comunicación, hoy pruebo y nada. No sé más cual sería el problema, o si mi placa ESPWrover quizá está defectuosa.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.