Dans ce projet, nous allons attacher l’ESP32-CAM à un support panoramique et inclinable avec deux servomoteurs SG90. Avec un support de caméra panoramique et inclinable, vous pouvez déplacer la caméra vers le haut, vers le bas, vers la gauche et la droite, ce qui est idéal pour la surveillance. L’ESP32-CAM héberge un serveur Web qui affiche le streaming vidéo et des boutons pour contrôler les servomoteurs pour déplacer la caméra.
Compatibilité des cartes : pour ce projet, vous avez besoin d’une carte de développement de caméra ESP32 avec
accès à deux GPIO pour contrôler deux servomoteurs. Vous pouvez utiliser: ESP32-CAM AI-Penseur, T-Journal ou Caméra T TTGO Plus.
Pièces requises
Pour ce projet, nous utiliserons les pièces suivantes :
Support et moteurs panoramiques et inclinables
Pour ce projet, nous utiliserons un support panoramique et inclinable qui est déjà livré avec deux servomoteurs SG90. Le support est illustré dans la figure suivante.
Nous avons notre stand chez Banggood, mais vous pouvez obtenir le vôtre dans n’importe quel autre magasin.
Alternativement, vous pouvez obtenir deux servomoteurs SG90 et imprimez en 3D votre propre stand.
Les servomoteurs ont trois fils de couleurs différentes :
Fil | Couleur |
Pouvoir | rouge |
GND | Noir ou marron |
Signal | Jaune, orange ou blanc |
Comment contrôler un servomoteur ?
Vous pouvez positionner l’arbre du servo à différents angles de 0 à 180º. Les servos sont contrôlés à l’aide d’un signal de modulation de largeur d’impulsion (PWM). Cela signifie que le signal PWM envoyé au moteur détermine la position de l’arbre.
Pour contrôler le servomoteur, vous pouvez utiliser les capacités PWM de l’ESP32 en envoyant un signal avec la largeur d’impulsion appropriée. Ou vous pouvez utiliser une bibliothèque pour simplifier le code. Nous utiliserons le ESP32Servo bibliothèque.
Installation de la bibliothèque ESP32Servo
Pour contrôler les servomoteurs, nous utiliserons le ESP32Servo bibliothèque. Assurez-vous d’installer cette bibliothèque avant de continuer. Dans votre IDE Arduino, accédez à Esquisser > Inclure la bibliothèque > Gérer les bibliothèques. Rechercher ESP32Servo et installez la bibliothèque comme indiqué ci-dessous.
Code
Copiez le code suivant dans votre IDE Arduino.
/*********
Rui Santos
Complete instructions at https://Raspberryme.com/esp32-cam-projects-ebook/
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*********/
#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" // disable brownout problems
#include "soc/rtc_cntl_reg.h" // disable brownout problems
#include "esp_http_server.h"
#include <ESP32Servo.h>
// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
#define PART_BOUNDARY "123456789000000000000987654321"
#define CAMERA_MODEL_AI_THINKER
//#define CAMERA_MODEL_M5STACK_PSRAM
//#define CAMERA_MODEL_M5STACK_WITHOUT_PSRAM
//#define CAMERA_MODEL_M5STACK_PSRAM_B
//#define CAMERA_MODEL_WROVER_KIT
#if defined(CAMERA_MODEL_WROVER_KIT)
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 21
#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 19
#define Y4_GPIO_NUM 18
#define Y3_GPIO_NUM 5
#define Y2_GPIO_NUM 4
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 23
#define PCLK_GPIO_NUM 22
#elif defined(CAMERA_MODEL_M5STACK_PSRAM)
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM 15
#define XCLK_GPIO_NUM 27
#define SIOD_GPIO_NUM 25
#define SIOC_GPIO_NUM 23
#define Y9_GPIO_NUM 19
#define Y8_GPIO_NUM 36
#define Y7_GPIO_NUM 18
#define Y6_GPIO_NUM 39
#define Y5_GPIO_NUM 5
#define Y4_GPIO_NUM 34
#define Y3_GPIO_NUM 35
#define Y2_GPIO_NUM 32
#define VSYNC_GPIO_NUM 22
#define HREF_GPIO_NUM 26
#define PCLK_GPIO_NUM 21
#elif defined(CAMERA_MODEL_M5STACK_WITHOUT_PSRAM)
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM 15
#define XCLK_GPIO_NUM 27
#define SIOD_GPIO_NUM 25
#define SIOC_GPIO_NUM 23
#define Y9_GPIO_NUM 19
#define Y8_GPIO_NUM 36
#define Y7_GPIO_NUM 18
#define Y6_GPIO_NUM 39
#define Y5_GPIO_NUM 5
#define Y4_GPIO_NUM 34
#define Y3_GPIO_NUM 35
#define Y2_GPIO_NUM 17
#define VSYNC_GPIO_NUM 22
#define HREF_GPIO_NUM 26
#define PCLK_GPIO_NUM 21
#elif 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
#elif defined(CAMERA_MODEL_M5STACK_PSRAM_B)
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM 15
#define XCLK_GPIO_NUM 27
#define SIOD_GPIO_NUM 22
#define SIOC_GPIO_NUM 23
#define Y9_GPIO_NUM 19
#define Y8_GPIO_NUM 36
#define Y7_GPIO_NUM 18
#define Y6_GPIO_NUM 39
#define Y5_GPIO_NUM 5
#define Y4_GPIO_NUM 34
#define Y3_GPIO_NUM 35
#define Y2_GPIO_NUM 32
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 26
#define PCLK_GPIO_NUM 21
#else
#error "Camera model not selected"
#endif
#define SERVO_1 14
#define SERVO_2 15
#define SERVO_STEP 5
Servo servoN1;
Servo servoN2;
Servo servo1;
Servo servo2;
int servo1Pos = 0;
int servo2Pos = 0;
static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY;
static const char* _STREAM_BOUNDARY = "rn--" PART_BOUNDARY "rn";
static const char* _STREAM_PART = "Content-Type: image/jpegrnContent-Length: %urnrn";
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;}
table { margin-left: auto; margin-right: auto; }
td { padding: 8 px; }
.button {
background-color: #2f4468;
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 ;
}
</style>
</head>
<body>
<h1>ESP32-CAM Pan and Tilt</h1>
<img src="" id="photo" >
<table>
<tr><td colspan="3" align="center"><button class="button" onmousedown="toggleCheckbox('up');" ontouchstart="toggleCheckbox('up');">Up</button></td></tr>
<tr><td align="center"><button class="button" onmousedown="toggleCheckbox('left');" ontouchstart="toggleCheckbox('left');">Left</button></td><td align="center"></td><td align="center"><button class="button" onmousedown="toggleCheckbox('right');" ontouchstart="toggleCheckbox('right');">Right</button></td></tr>
<tr><td colspan="3" align="center"><button class="button" onmousedown="toggleCheckbox('down');" ontouchstart="toggleCheckbox('down');">Down</button></td></tr>
</table>
<script>
function toggleCheckbox(x) {
var xhr = new XMLHttpRequest();
xhr.open("GET", "/action?go=" + x, true);
xhr.send();
}
window.onload = document.getElementById("photo").src = window.location.href.slice(0, -1) + ":81/stream";
</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;
}
//Serial.printf("MJPG: %uBn",(uint32_t)(_jpg_buf_len));
}
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();
//flip the camera vertically
//s->set_vflip(s, 1); // 0 = disable , 1 = enable
// mirror effect
//s->set_hmirror(s, 1); // 0 = disable , 1 = enable
int res = 0;
if(!strcmp(variable, "up")) {
if(servo1Pos <= 170) {
servo1Pos += 10;
servo1.write(servo1Pos);
}
Serial.println(servo1Pos);
Serial.println("Up");
}
else if(!strcmp(variable, "left")) {
if(servo2Pos <= 170) {
servo2Pos += 10;
servo2.write(servo2Pos);
}
Serial.println(servo2Pos);
Serial.println("Left");
}
else if(!strcmp(variable, "right")) {
if(servo2Pos >= 10) {
servo2Pos -= 10;
servo2.write(servo2Pos);
}
Serial.println(servo2Pos);
Serial.println("Right");
}
else if(!strcmp(variable, "down")) {
if(servo1Pos >= 10) {
servo1Pos -= 10;
servo1.write(servo1Pos);
}
Serial.println(servo1Pos);
Serial.println("Down");
}
else {
res = -1;
}
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); //disable brownout detector
servo1.setPeriodHertz(50); // standard 50 hz servo
servo2.setPeriodHertz(50); // standard 50 hz servo
servoN1.attach(2, 1000, 2000);
servoN2.attach(13, 1000, 2000);
servo1.attach(SERVO_1, 1000, 2000);
servo2.attach(SERVO_2, 1000, 2000);
servo1.write(servo1Pos);
servo2.write(servo2Pos);
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() {
}
Identifiants du réseau
Insérez vos informations d’identification réseau et le code devrait fonctionner immédiatement.
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
Comment fonctionne le code
Jetons un coup d’œil aux pièces pertinentes pour contrôler les servomoteurs.
Définissez les broches auxquelles les servomoteurs sont connectés. Dans ce cas, ils sont connectés aux GPIO ESP32-CAM 14 et 15.
#define SERVO_1 14
#define SERVO_2 15
Créer Servomoteur objets pour contrôler chaque moteur :
Servo servoN1;
Servo servoN2;
Servo servo1;
Servo servo2;
Vous vous demandez peut-être pourquoi nous créons quatre Servomoteur objets lorsque nous n’avons que deux servos. Ce qui se passe, c’est que la bibliothèque d’asservissement que nous utilisons attribue automatiquement un canal PWM à chaque servomoteur (servoN1 → canal PWM 0; servoN2 → canal PWM 1; servo1 → canal PWM 2; servo2 → canal PWM 3).
Les premiers canaux sont utilisés par la caméra, donc si nous modifions les propriétés de ces canaux PWM, nous obtiendrons des erreurs avec la caméra. Alors, on contrôlera servo1 et servo2 qui utilisent les canaux PWM 2 et 3 qui ne sont pas utilisés par la caméra.
Définir la position initiale des servos.
int servo1Pos = 0;
int servo2Pos = 0;
Page Web
le INDEX_HTML La variable contient le texte HTML pour construire la page Web. Les lignes suivantes affichent les boutons.
<table>
<tr><td colspan="3" align="center"><button class="button" onmousedown="toggleCheckbox('up');" ontouchstart="toggleCheckbox('up');">Up</button></td></tr>
<tr><td align="center"><button class="button" onmousedown="toggleCheckbox('left');" ontouchstart="toggleCheckbox('left');">Left</button></td><td align="center"></td><td align="center"><button class="button" onmousedown="toggleCheckbox('right');" ontouchstart="toggleCheckbox('right');">Right</button></td></tr>
<tr><td colspan="3" align="center"><button class="button" onmousedown="toggleCheckbox('down');" ontouchstart="toggleCheckbox('down');">Down</button></td></tr>
</table>
Lorsque vous cliquez sur les boutons, le toggleCheckbox() La fonction JavaScript est appelée. Il fait une requête sur une URL différente selon le bouton cliqué.
function toggleCheckbox(x) {
var xhr = new XMLHttpRequest();
xhr.open("GET", "/action?go=" + x, true);
xhr.send();
}
Voici les demandes effectuées en fonction du bouton sur lequel on appuie :
En haut:
/action?go=up
Vers le bas:
/action?go=down
À gauche:
/action?go=left
À droite:
/action?go=right
Traiter les demandes
Ensuite, nous devons gérer ce qui se passe lorsque nous recevons ces demandes. C’est ce qui est fait dans les lignes suivantes.
if(!strcmp(variable, "up")) {
if(servo1Pos <= 170) {
servo1Pos += 10;
servo1.write(servo1Pos);
}
Serial.println(servo1Pos);
Serial.println("Up");
}
else if(!strcmp(variable, "left")) {
if(servo2Pos <= 170) {
servo2Pos += 10;
servo2.write(servo2Pos);
}
Serial.println(servo2Pos);
Serial.println("Left");
}
else if(!strcmp(variable, "right")) {
if(servo2Pos >= 10) {
servo2Pos -= 10;
servo2.write(servo2Pos);
}
Serial.println(servo2Pos);
Serial.println("Right");
}
else if(!strcmp(variable, "down")) {
if(servo1Pos >= 10) {
servo1Pos -= 10;
servo1.write(servo1Pos);
}
Serial.println(servo1Pos);
Serial.println("Down");
}
Pour déplacer un moteur, appelez le écrivez() fonction sur le servo1 ou servo2 objets et passez l’angle (0 à 180) comme argument. Par example:
servo1.write(servo1Pos);
mettre en place()
Dans le mettre en place(), définissez les propriétés du servomoteur : définissez la fréquence du signal.
servo1.setPeriodHertz(50); // standard 50 hz servo
servo2.setPeriodHertz(50); // standard 50 hz servo
Utilisez le attacher() méthode pour définir le servo GPIO et la largeur d’impulsion minimale et maximale en microsecondes.
servo1.attach(SERVO_1, 1000, 2000);
servo2.attach(SERVO_2, 1000, 2000);
Réglez les moteurs sur leur position initiale lorsque l’ESP32 démarre pour la première fois.
servo1.write(servo1Pos);
servo2.write(servo2Pos);
C’est à peu près ainsi que fonctionne le code lorsqu’il s’agit de contrôler les servomoteurs.
Tester le code
Après avoir inséré vos informations d’identification réseau, vous pouvez télécharger le code sur votre tableau. Vous pouvez utiliser un programmeur FTDI ou un programmeur ESP32-CAM MB. Lisez l’un des articles suivants :
Après le téléchargement, ouvrez le moniteur série pour obtenir l’adresse IP de la carte.
Noter: si vous utilisez un programmeur FTDI, n’oubliez pas de déconnecter GPIO 0 de GND avant d’ouvrir Serial Monitor.
Ouvrez un navigateur et saisissez l’adresse IP de la carte pour accéder au serveur Web. Cliquez sur les boutons et vérifiez sur le moniteur série si tout semble fonctionner comme prévu.
Si tout fonctionne comme prévu, vous pouvez câbler les servomoteurs à l’ESP32-CAM et poursuivre le projet.
Circuit
Après avoir assemblé le support panoramique et inclinable, connectez les servomoteurs à l’ESP32-CAM comme indiqué dans le schéma suivant. Nous connectons les broches de données du servomoteur à GPIO 15 et GPIO 14.
Vous pouvez utiliser une mini planche à pain pour assembler le circuit ou construire une mini planche à ruban avec des broches d’en-tête pour connecter l’alimentation, l’ESP32-CAM et les moteurs, comme indiqué ci-dessous.
La figure suivante montre à quoi ressemblent les supports panoramiques et inclinables après assemblage.
Manifestation
Mettez votre carte sous tension. Ouvrez un navigateur et saisissez l’adresse IP ESP32-CAM. Une page Web avec un streaming vidéo en temps réel devrait se charger. Cliquez sur les boutons pour déplacer la caméra vers le haut, le bas, la gauche ou la droite.
Vous pouvez déplacer la caméra à distance à l’aide des boutons de la page Web. Cela vous permet de
surveiller une zone différente en fonction de la position de la caméra. C’est une excellente solution pour les applications de surveillance.
Emballer
Dans ce didacticiel, vous avez appris à créer un serveur Web panoramique et inclinable avec streaming vidéo pour contrôler l’ESP32-CAM.
Contrôler les servomoteurs avec l’ESP32-CAM revient à les contrôler à l’aide d’un ESP32 « normal ». Vous pouvez lire le tutoriel suivant pour en savoir plus sur les servomoteurs avec l’ESP32 :
Si vous souhaitez contrôler votre robot en dehors de la portée de votre réseau local, vous pouvez envisager de configurer l’ESP32-CAM comme point d’accès. De cette façon, l’ESP32-CAM n’a pas besoin de se connecter à votre routeur. Il crée son propre réseau Wi-Fi et les appareils Wi-Fi à proximité, comme votre smartphone, peuvent s’y connecter.
Pour plus de projets et de tutoriels avec l’ESP32-CAM :