Robot disuasorio

Grupo 17

Hecho por:

  • EOUZAN Erwan
  • LE RAY Thomas
  • LEBRET Flavien

Introducción

Por el marco del módulo «Sistemas Empotrados y de Tiempo Real», tuvimos la tarea de realizar un proyecto Arduino. Después de discutir entre los miembros del grupo, los tres teníamos el deseo de llevar a cabo un proyecto Arduino que nos apasionara. Una noche, mientras conversábamos, Thomas nos contó la historia de un robo cerca de su casa, lo que dio origen a nuestra idea de proyecto: un robot que se mueve, se controla a distancia y asusta! Aquí les presentaremos el proyecto en todos sus aspectos.

Materiales usado

Pasos dados

El primer paso en la realización de nuestro proyecto fue definirlo adecuadamente, tener una idea del material necesario, de los subsistemas a implementar (costos y riesgos para cada uno), así como definir los objetivos del proyecto. Aquí están los objetivos del proyecto que nos propusimos:

  • El robot debe poder controlarse (4 direcciones) a través de una página web (WIFI)
  • El usuario debe poder realizar a distancia acciones como encender la luz o hacer ruido.
  • El robot debe poder utilizarse de día y de noche. 
  • El usuario debe poder controlar la torreta

Entonces pudimos crear un primer diagrama de sistema, con los componentes y las conexiones. A continuación, puedes ver el primer diagrama de sistema que elaboramos:

Figura: Primer diagrama de conexión del sistema

Luego, probamos todos nuestros subsistemas individualmente para asegurarnos de su correcto funcionamiento. Una vez que estuvimos seguros de que cada subsistema funcionaba correctamente, pudimos ensamblarlos para formar el robot. Este ensamblaje nos permitió optimizar el diagrama de conexión y entender bien cómo colocar los diferentes componentes. En este punto, el robot funcionaba y estaba ensamblado. Por lo tanto, pudimos validar con respecto a los objetivos que nos habíamos fijado y finalizar el conjunto asegurando firmemente.

Diagrama de conexión

Software / Code

#include "esp_camera.h"
#include <WiFi.h>
#include "esp_timer.h"
#include "img_converters.h"
#include "Arduino.h"
#include "fb_gfx.h"
#include "soc/soc.h"             
#include "soc/rtc_cntl_reg.h"    
#include "esp_http_server.h"
#include <ESP32Servo.h>


const char* ssid = "ErwanPOA";
const char* password = "******";

#define PART_BOUNDARY "123456789000000000000987654321"

#define CAMERA_MODEL_AI_THINKER


#if defined(CAMERA_MODEL_AI_THINKER)
  #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

#else
  #error "Camera model not selected"
#endif

#define MOTOR_1_PIN_1    14
#define MOTOR_1_PIN_2    15
#define MOTOR_2_PIN_1    13
#define MOTOR_2_PIN_2    12
#define LIGHT_PIN        4
#define SERVO_PIN        2
Servo servo;





static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY;
static const char* _STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
static const char* _STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n";
int lig=0;


httpd_handle_t camera_httpd = NULL;
httpd_handle_t stream_httpd = NULL;

static const char PROGMEM INDEX_HTML[] = R"rawliteral(
<html>
<head>
  <title>ESP32-CAM Robot</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <style>
    body {
      font-family: Arial;
      text-align: center;
      margin: 0px auto;
      padding-top: 30px;
      background-color: #f0f0f0; /* Couleur de fond */
    }
    h1 {
      color: white; /* Texte blanc */
      background-color: #333; /* Fond gris foncé */
      padding: 20px; /* Ajoute de l'espace autour du titre */
    }
    table {
      margin-left: auto;
      margin-right: auto;
      margin-top: 20px; /* Espace entre le titre et le tableau */
    }
    td {
      padding: 8px;
    }
    .button {
      background-color: #333;
      border: none;
      color: white;
      padding: 10px 20px;
      text-align: center;
      text-decoration: none;
      display: inline-block;
      font-size: 18px;
      margin: 6px 3px;
      cursor: pointer;
      -webkit-touch-callout: none;
      -webkit-user-select: none;
      -khtml-user-select: none;
      -moz-user-select: none;
      -ms-user-select: none;
      user-select: none;
      -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
    }
    img {
      width: auto;
      max-width: 100%;
      height: auto;
      margin-top: 20px; /* Espace entre le tableau et l'image */
    }
    .title {
      background-color: #333;
      color: white;
      padding: 10px;
      font-size: 24px;
      margin-bottom: 10px; /* Espace entre les titres et le contenu */
      margin-top: 20px; /* Ajoutez cette ligne pour l'espace entre le titre et l'image */
    }
    .slider-container {
      margin-top: 20px;
    }
    .slider {
      -webkit-appearance: none;
      width: 80%;
      height: 10px;
      border-radius: 5px;
      background: #333;
      outline: none;
      opacity: 0.7;
      -webkit-transition: .2s;
      transition: opacity .2s;
      margin: auto;
    }
    .slider:hover {
      opacity: 1;
    }
    .slider::-webkit-slider-thumb {
      -webkit-appearance: none;
      appearance: none;
      width: 20px;
      height: 20px;
      border-radius: 50%;
      background: #ffffff;
      cursor: pointer;
    }
    .slider::-moz-range-thumb {
      width: 20px;
      height: 20px;
      border-radius: 50%;
      background: #ffffff;
      cursor: pointer;
    }
    .slider-value-bg {
    background-color: #333; /* Couleur de fond */
    color: white; /* Couleur du texte */
    padding: 4px 8px; /* Ajoute de l'espace autour du texte */
    border-radius: 4px; /* Coins arrondis */
  }

  </style>
</head>
<body>
  <h1>ESP32-CAM Robot</h1>

  <img src="" id="photo">

  <div class="title">Camera moove</div>
  <div class="slider-container">
  <input type="range" min="60" max="160" value="140" class="slider" id="camera-slider">
  <span id="slider-value" class="slider-value-bg">95</span>
  </div>

  <div class="title">Moove Robot</div>

  <table>
    <tr>
      <td colspan="3" align="center">
        <button class="button" onmousedown="toggleCheckbox('forward');" ontouchstart="toggleCheckbox('forward');" onmouseup="toggleCheckbox('stop');" ontouchend="toggleCheckbox('stop');">Forward</button>
      </td>
    </tr>
    <tr>
      <td align="center">
        <button class="button" onmousedown="toggleCheckbox('left');" ontouchstart="toggleCheckbox('left');" onmouseup="toggleCheckbox('stop');" ontouchend="toggleCheckbox('stop');">Left</button>
      </td>
      <td align="center">
        <button class="button" onmousedown="toggleCheckbox('right');" ontouchstart="toggleCheckbox('right');" onmouseup="toggleCheckbox('stop');" ontouchend="toggleCheckbox('stop');">Right</button>
      </td>
    </tr>
    <tr>
      <td colspan="3" align="center">
        <button class="button" onmousedown="toggleCheckbox('backward');" ontouchstart="toggleCheckbox('backward');" onmouseup="toggleCheckbox('stop');" ontouchend="toggleCheckbox('stop');">Backward</button>
      </td>
    </tr>
  </table>

  <!-- Titre "asustar" -->
  <div class="title">Asustar</div>

  <table>
    <tr>
      <td colspan="3" align="center">
        <button class="button" onmousedown="toggleCheckbox('light');" ontouchstart="toggleCheckbox('light');">Light and buzzer</button>
      </td>
    </tr>
  </table>

  <script>
    function toggleCheckbox(x) {
      var xhr = new XMLHttpRequest();
      xhr.open("GET", "/action?go=" + x, true);
      xhr.send();
    }

    window.onload = function() {
      document.getElementById("photo").src = window.location.href.slice(0, -1) + ":81/stream";
    };

    function updateSliderValue() {
      var slider = document.getElementById("camera-slider");
      var valueSpan = document.getElementById("slider-value");
      valueSpan.innerHTML = slider.value;
      toggleCheckbox(slider.value);
    }

    document.getElementById("camera-slider").addEventListener("input", updateSliderValue);

    updateSliderValue();
    
  </script>
</body>
</html>
)rawliteral";


static esp_err_t index_handler(httpd_req_t *req){
  httpd_resp_set_type(req, "text/html");
  return httpd_resp_send(req, (const char *)INDEX_HTML, strlen(INDEX_HTML));
}

static esp_err_t stream_handler(httpd_req_t *req){
  camera_fb_t * fb = NULL;
  esp_err_t res = ESP_OK;
  size_t _jpg_buf_len = 0;
  uint8_t * _jpg_buf = NULL;
  char * part_buf[64];

  res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE);
  if(res != ESP_OK){
    return res;
  }

  while(true){
    fb = esp_camera_fb_get();
    if (!fb) {
      Serial.println("Camera capture failed");
      res = ESP_FAIL;
    } else {
      if(fb->width > 400){
        if(fb->format != PIXFORMAT_JPEG){
          bool jpeg_converted = frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len);
          esp_camera_fb_return(fb);
          fb = NULL;
          if(!jpeg_converted){
            Serial.println("JPEG compression failed");
            res = ESP_FAIL;
          }
        } else {
          _jpg_buf_len = fb->len;
          _jpg_buf = fb->buf;
        }
      }
    }
    if(res == ESP_OK){
      size_t hlen = snprintf((char *)part_buf, 64, _STREAM_PART, _jpg_buf_len);
      res = httpd_resp_send_chunk(req, (const char *)part_buf, hlen);
    }
    if(res == ESP_OK){
      res = httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len);
    }
    if(res == ESP_OK){
      res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
    }
    if(fb){
      esp_camera_fb_return(fb);
      fb = NULL;
      _jpg_buf = NULL;
    } else if(_jpg_buf){
      free(_jpg_buf);
      _jpg_buf = NULL;
    }
    if(res != ESP_OK){
      break;
    }
  }
  return res;
}

static esp_err_t cmd_handler(httpd_req_t *req){
  char*  buf;
  size_t buf_len;
  char variable[32] = {0,};
  
  buf_len = httpd_req_get_url_query_len(req) + 1;
  if (buf_len > 1) {
    buf = (char*)malloc(buf_len);
    if(!buf){
      httpd_resp_send_500(req);
      return ESP_FAIL;
    }
    if (httpd_req_get_url_query_str(req, buf, buf_len) == ESP_OK) {
      if (httpd_query_key_value(buf, "go", variable, sizeof(variable)) == ESP_OK) {
      } else {
        free(buf);
        httpd_resp_send_404(req);
        return ESP_FAIL;
      }
    } else {
      free(buf);
      httpd_resp_send_404(req);
      return ESP_FAIL;
    }
    free(buf);
  } else {
    httpd_resp_send_404(req);
    return ESP_FAIL;
  }

  sensor_t * s = esp_camera_sensor_get();
  int res = 0;
  
  if(!strcmp(variable, "forward")) {
    Serial.println("Forward");
    digitalWrite(MOTOR_1_PIN_1, 0);
    digitalWrite(MOTOR_1_PIN_2, 1);
    digitalWrite(MOTOR_2_PIN_1, 0);
    digitalWrite(MOTOR_2_PIN_2, 1);
  }
  else if(!strcmp(variable, "left")) {
    Serial.println("Left");
    digitalWrite(MOTOR_1_PIN_1, 0);
    digitalWrite(MOTOR_1_PIN_2, 1);
    digitalWrite(MOTOR_2_PIN_1, 1);
    digitalWrite(MOTOR_2_PIN_2, 0);
  }
  else if(!strcmp(variable, "right")) {
    Serial.println("Right");
    digitalWrite(MOTOR_1_PIN_1, 1);
    digitalWrite(MOTOR_1_PIN_2, 0);
    digitalWrite(MOTOR_2_PIN_1, 0);
    digitalWrite(MOTOR_2_PIN_2, 1);
  }
  else if(!strcmp(variable, "backward")) {
    Serial.println("Backward");
    digitalWrite(MOTOR_1_PIN_1, 1);
    digitalWrite(MOTOR_1_PIN_2, 0);
    digitalWrite(MOTOR_2_PIN_1, 1);
    digitalWrite(MOTOR_2_PIN_2, 0);
  }
  else if(!strcmp(variable, "stop")) {
    Serial.println("Stop");
    digitalWrite(MOTOR_1_PIN_1, 0);
    digitalWrite(MOTOR_1_PIN_2, 0);
    digitalWrite(MOTOR_2_PIN_1, 0);
    digitalWrite(MOTOR_2_PIN_2, 0);
  }
  else if(!strcmp(variable, "light")) {
    delay(100);
    Serial.println(lig);
    if (lig==0){
      lig = 1;
      Serial.println("Light On");
      digitalWrite(LIGHT_PIN, HIGH);
      delay(50);
      digitalWrite(LIGHT_PIN, LOW);
      delay(50);
      digitalWrite(LIGHT_PIN, HIGH);
      delay(50);
      digitalWrite(LIGHT_PIN, LOW);
      delay(50);
      digitalWrite(LIGHT_PIN, HIGH);
      delay(50);
      digitalWrite(LIGHT_PIN, HIGH);
    }
    else{
      lig=0;
      Serial.println("Light Off");
      digitalWrite(LIGHT_PIN, LOW);
    } 
  }



  else {
    Serial.println(variable);
    int var = atoi(variable);
    servo.write(var);
  }

  if(res){
    return httpd_resp_send_500(req);
  }

  httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
  return httpd_resp_send(req, NULL, 0);
}

void startCameraServer(){
  httpd_config_t config = HTTPD_DEFAULT_CONFIG();
  config.server_port = 80;
  httpd_uri_t index_uri = {
    .uri       = "/",
    .method    = HTTP_GET,
    .handler   = index_handler,
    .user_ctx  = NULL
  };

  httpd_uri_t cmd_uri = {
    .uri       = "/action",
    .method    = HTTP_GET,
    .handler   = cmd_handler,
    .user_ctx  = NULL
  };
  httpd_uri_t stream_uri = {
    .uri       = "/stream",
    .method    = HTTP_GET,
    .handler   = stream_handler,
    .user_ctx  = NULL
  };
  if (httpd_start(&camera_httpd, &config) == ESP_OK) {
    httpd_register_uri_handler(camera_httpd, &index_uri);
    httpd_register_uri_handler(camera_httpd, &cmd_uri); 
  }
  config.server_port += 1;
  config.ctrl_port += 1;
  if (httpd_start(&stream_httpd, &config) == ESP_OK) {
    httpd_register_uri_handler(stream_httpd, &stream_uri);
  }
}

void setup() {
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); 
  
  pinMode(MOTOR_1_PIN_1, OUTPUT);
  pinMode(MOTOR_1_PIN_2, OUTPUT);
  pinMode(MOTOR_2_PIN_1, OUTPUT);
  pinMode(MOTOR_2_PIN_2, OUTPUT);
  pinMode(LIGHT_PIN, OUTPUT); 
  servo.attach(SERVO_PIN);

  
  Serial.begin(115200);
  Serial.setDebugOutput(false);
  
  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; 
  
  if(psramFound()){
    config.frame_size = FRAMESIZE_VGA;
    config.jpeg_quality = 10;
    config.fb_count = 2;
  } else {
    config.frame_size = FRAMESIZE_SVGA;
    config.jpeg_quality = 12;
    config.fb_count = 1;
  }
  
  // Camera init
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    return;
  }
  // Wi-Fi connection
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  
  Serial.print("Camera Stream Ready! Go to: http://");
  Serial.println(WiFi.localIP());
  
  // Start streaming web server
  startCameraServer();
}

void loop() {
  
}

Problemas Encontrados

  • Componentes que no funcionan (cables, zumbador)
  • Componentes rotos (ESP32 cámara, servomotor)
  • Montaje difícil de realizar para que el peso esté equilibrado y todo permanezca firmemente fijado
  • Problema de potencia en los motores para hacer avanzar correctamente el robot
  • Espera de la llegada de los componentes (compras por internet)
  • Desarrollo de la interfaz web (esto tomó mucho tiempo)

Posibles Mejoras

  • Agregar nuevos componentes para tener otras formas de asustar
  • Creación de un modelo 3D para estética y asustar
  • Agregar reconocimiento facial para distinguir a un ladrón de un residente de la casa
  • Robot con visión nocturna
  • Modo autónomo → No se necesita a nadie para controlar el robot, que puede moverse solo en la casa según un patrón deseado
  • Robot conectado a sensores de movimiento para moverse donde haya habido movimiento

Funcionamiento del robot

Aquí explicaremos cómo funciona nuestro robot. En primer lugar, es necesario adaptar el código al entorno de la casa para permitir que el robot acceda al WIFI (SSID + contraseña) y así permitir el control remoto. Una vez hecho esto, podemos conectarnos a nuestra interfaz web para poder controlar el robot.

En la interfaz web, recuperamos en tiempo real a través del wifi el vídeo capturado por la cámara ESP32. Es posible modificar la vista de la cámara moviendo la barra lateral. Mover la barra lateral cambiará el ángulo del servomotor en el que está fijada la cámara.

Luego, tenemos la opción de mover el robot en 4 direcciones (adelante, atrás, derecha, izquierda). Para ello, simplemente mantenemos presionada la dirección deseada y soltamos cuando queremos dejar de moverlo.

Finalmente, el botón inferior enciende los comportamientos que asustan: después de presionar, suena un sonido fuerte y se enciende una luz brillante. Hay que volver a presionar para detener esto. Puedes ver el video a continuación para visualizar todo esto.

Vídeo explicativo

También te podría gustar...

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *