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.