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:
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